├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── git_toolbox_prj.xml ├── google-java-format.xml ├── gradle.xml ├── intellij-javadocs-4.0.1.xml ├── ktfmt.xml ├── ktlint.xml ├── misc.xml └── vcs.xml ├── .vscode └── settings.json ├── HOW TO USE.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── docubox.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── docubox │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_logo-playstore.png │ ├── java │ │ └── com │ │ │ └── docubox │ │ │ ├── MyApp.kt │ │ │ ├── data │ │ │ ├── local │ │ │ │ ├── StorageCache.kt │ │ │ │ ├── dataSources │ │ │ │ │ └── dataStore │ │ │ │ │ │ ├── DataStoreManager.kt │ │ │ │ │ │ └── PreferencesManager.kt │ │ │ │ └── models │ │ │ │ │ ├── FileOption.kt │ │ │ │ │ ├── FileType.kt │ │ │ │ │ ├── FolderOptions.kt │ │ │ │ │ ├── SearchResult.kt │ │ │ │ │ ├── StorageItem.kt │ │ │ │ │ └── User.kt │ │ │ ├── mapper │ │ │ │ ├── FileMapper.kt │ │ │ │ ├── FolderMapper.kt │ │ │ │ └── UserMapper.kt │ │ │ ├── remote │ │ │ │ ├── api │ │ │ │ │ ├── AuthService.kt │ │ │ │ │ └── StorageService.kt │ │ │ │ ├── dataSources │ │ │ │ │ ├── AuthDataSource.kt │ │ │ │ │ └── StorageDataSource.kt │ │ │ │ └── models │ │ │ │ │ ├── MessageResponse.kt │ │ │ │ │ ├── UserDto.kt │ │ │ │ │ ├── requests │ │ │ │ │ ├── CreateFolderRequest.kt │ │ │ │ │ ├── GetFileRequest.kt │ │ │ │ │ ├── GetFolderRequest.kt │ │ │ │ │ ├── LoginRequest.kt │ │ │ │ │ ├── RegisterRequest.kt │ │ │ │ │ ├── RenameFileRequest.kt │ │ │ │ │ ├── RenameFolderRequest.kt │ │ │ │ │ ├── RevokeFileRequest.kt │ │ │ │ │ └── ShareFileRequest.kt │ │ │ │ │ └── responses │ │ │ │ │ ├── OwnerDto.kt │ │ │ │ │ ├── ParentDirectory.kt │ │ │ │ │ ├── StorageConsumption.kt │ │ │ │ │ ├── file │ │ │ │ │ ├── FileDto.kt │ │ │ │ │ ├── FileListResponse.kt │ │ │ │ │ ├── UploadFileResponse.kt │ │ │ │ │ └── UploadedFile.kt │ │ │ │ │ └── folder │ │ │ │ │ ├── CreateFolderResponse.kt │ │ │ │ │ ├── FolderDto.kt │ │ │ │ │ └── GetFolderResponse.kt │ │ │ └── repo │ │ │ │ ├── AuthRepoImpl.kt │ │ │ │ ├── PreferencesRepoImpl.kt │ │ │ │ └── StorageRepoImpl.kt │ │ │ ├── di │ │ │ ├── LocalModule.kt │ │ │ └── NetworkModule.kt │ │ │ ├── domain │ │ │ └── repo │ │ │ │ ├── AuthRepo.kt │ │ │ │ ├── PreferenceRepo.kt │ │ │ │ └── StorageRepo.kt │ │ │ ├── service │ │ │ └── FileUploadService.kt │ │ │ ├── ui │ │ │ ├── adapter │ │ │ │ ├── AbstractAdapter.kt │ │ │ │ ├── DiffUtilCallback.kt │ │ │ │ └── OneAdapter.kt │ │ │ └── screens │ │ │ │ ├── auth │ │ │ │ ├── AuthActivity.kt │ │ │ │ ├── gettingStarted │ │ │ │ │ └── GettingStartedFragment.kt │ │ │ │ ├── login │ │ │ │ │ ├── LoginFragment.kt │ │ │ │ │ ├── LoginScreenEvents.kt │ │ │ │ │ ├── LoginScreenState.kt │ │ │ │ │ └── LoginViewModel.kt │ │ │ │ └── register │ │ │ │ │ ├── RegisterFragment.kt │ │ │ │ │ ├── RegisterScreenEvents.kt │ │ │ │ │ ├── RegisterScreenState.kt │ │ │ │ │ └── RegisterViewModel.kt │ │ │ │ ├── dialogs │ │ │ │ ├── AboutUsBottomSheetFragment.kt │ │ │ │ ├── FileOptionsBottomSheetFragment.kt │ │ │ │ └── FolderOptionsBottomSheetFragment.kt │ │ │ │ ├── main │ │ │ │ ├── DocuBoxActivity.kt │ │ │ │ ├── documents │ │ │ │ │ ├── DocumentsFragment.kt │ │ │ │ │ ├── DocumentsScreenEvents.kt │ │ │ │ │ ├── DocumentsScreenState.kt │ │ │ │ │ └── DocumentsViewModel.kt │ │ │ │ ├── home │ │ │ │ │ ├── HomeFragment.kt │ │ │ │ │ ├── HomeScreenEvents.kt │ │ │ │ │ ├── HomeScreenState.kt │ │ │ │ │ └── HomeViewModel.kt │ │ │ │ ├── others │ │ │ │ │ └── ViewDocumentFragment.kt │ │ │ │ ├── profile │ │ │ │ │ ├── ProfileFragment.kt │ │ │ │ │ └── ProfileViewModel.kt │ │ │ │ ├── searchResults │ │ │ │ │ ├── SearchResultsFragment.kt │ │ │ │ │ ├── SearchResultsScreenEvents.kt │ │ │ │ │ ├── SearchResultsScreenState.kt │ │ │ │ │ └── SearchResultsViewModel.kt │ │ │ │ └── shared │ │ │ │ │ ├── SharedFragment.kt │ │ │ │ │ ├── SharedScreenEvents.kt │ │ │ │ │ ├── SharedScreenState.kt │ │ │ │ │ └── SharedViewModel.kt │ │ │ │ └── splash │ │ │ │ ├── SplashActivity.kt │ │ │ │ ├── SplashScreenEvents.kt │ │ │ │ └── SplashViewModel.kt │ │ │ └── util │ │ │ ├── Constants.kt │ │ │ ├── CredentialsValidator.kt │ │ │ ├── FileUtil.kt │ │ │ ├── NotificationHelper.kt │ │ │ ├── Resource.kt │ │ │ ├── SafeApiCall.kt │ │ │ ├── SafeCall.kt │ │ │ ├── Secrets.kt │ │ │ ├── extensions │ │ │ ├── ContextExtensions.kt │ │ │ ├── Extensions.kt │ │ │ ├── FileOptionsExt.kt │ │ │ ├── FlowExt.kt │ │ │ ├── FolderOptionsExt.kt │ │ │ ├── ResourceExt.kt │ │ │ └── ViewExtensions.kt │ │ │ └── viewBinding │ │ │ ├── FragmentViewBindingDelegate.kt │ │ │ └── ViewBindingDelegate.kt │ └── res │ │ ├── drawable-v24 │ │ ├── ic_audio.png │ │ ├── ic_document.png │ │ ├── ic_file.png │ │ ├── ic_folder.png │ │ ├── ic_image.png │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_video.png │ │ ├── img_about.png │ │ ├── img_avatar.png │ │ ├── img_contact.png │ │ ├── img_documentation.png │ │ ├── img_downloads.png │ │ └── img_lock.png │ │ ├── drawable │ │ ├── app_logo.jpeg │ │ ├── et_home_search.xml │ │ ├── ic_add.xml │ │ ├── ic_back.xml │ │ ├── ic_baseline_add_24.xml │ │ ├── ic_bug.xml │ │ ├── ic_change_avatar.xml │ │ ├── ic_code.xml │ │ ├── ic_create_folder.xml │ │ ├── ic_document_icon.xml │ │ ├── ic_downloads.xml │ │ ├── ic_edit.xml │ │ ├── ic_file_upload.xml │ │ ├── ic_grid.xml │ │ ├── ic_home.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_logout.xml │ │ ├── ic_profile.xml │ │ ├── ic_refresh.xml │ │ ├── ic_search.xml │ │ ├── ic_server_status.xml │ │ ├── ic_shared_folder.xml │ │ ├── ic_upload_folder.xml │ │ ├── ripple_dark.xml │ │ └── ripple_default.xml │ │ ├── font │ │ ├── popping_extra_bold.ttf │ │ ├── poppins_black.ttf │ │ ├── poppins_bold.ttf │ │ ├── poppins_light.ttf │ │ ├── poppins_medium.ttf │ │ ├── poppins_regular.ttf │ │ └── poppins_semi_bold.ttf │ │ ├── layout │ │ ├── action_bar.xml │ │ ├── activity_auth.xml │ │ ├── activity_docubox.xml │ │ ├── activity_splash.xml │ │ ├── empty_state.xml │ │ ├── empty_state2.xml │ │ ├── fragment_about_us_bottom_sheet.xml │ │ ├── fragment_documents.xml │ │ ├── fragment_getting_started.xml │ │ ├── fragment_home.xml │ │ ├── fragment_login.xml │ │ ├── fragment_profile.xml │ │ ├── fragment_register.xml │ │ ├── fragment_search_results.xml │ │ ├── fragment_shared.xml │ │ ├── fragment_view_document.xml │ │ ├── item_options.xml │ │ ├── item_storage.xml │ │ ├── options_bottom_sheet.xml │ │ ├── progress_bar.xml │ │ ├── sheet_upload_document.xml │ │ └── text_input_dialog.xml │ │ ├── menu │ │ └── bottom_nav_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_logo.xml │ │ └── ic_logo_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_logo.png │ │ ├── ic_logo_foreground.png │ │ └── ic_logo_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_logo.png │ │ ├── ic_logo_foreground.png │ │ └── ic_logo_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_logo.png │ │ ├── ic_logo_foreground.png │ │ └── ic_logo_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_logo.png │ │ ├── ic_logo_foreground.png │ │ └── ic_logo_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_logo.png │ │ ├── ic_logo_foreground.png │ │ └── ic_logo_round.png │ │ ├── navigation │ │ ├── auth_nav_graph.xml │ │ └── main_nav_graph.xml │ │ ├── raw │ │ └── documents_anim.json │ │ ├── values │ │ ├── colors.xml │ │ ├── dimen.xml │ │ ├── ic_logo_background.xml │ │ ├── strings.xml │ │ ├── themes.xml │ │ └── typography.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── docubox │ ├── ExampleUnitTest.kt │ └── data │ └── local │ └── fileEncryptor │ └── AliceFileEncryptorTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── media ├── architecture.png ├── cover.png ├── graphicA.png ├── graphicB.png ├── graphicC.png ├── package structure.png ├── screenshots │ ├── about_us.jpg │ ├── document.jpg │ ├── documents_2.jpg │ ├── getting_started.jpg │ ├── home.jpg │ ├── login.jpg │ ├── profile.jpg │ ├── register.jpg │ ├── shared_by_you.jpg │ ├── shared_to_me.jpg │ ├── splash.jpg │ └── videos.jpg └── summary.png └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Android Phone (please complete the following information):** 27 | - Device: [e.g. iSamsung S21] 28 | - OS: [e.g. Android 13] 29 | - Version [e.g. 1.0.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Req]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.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/java/com/docubox/util/EncryptionDetails.kt 17 | app/src/main/java/com/docubox/util/Secrets.kt -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | DocuBox -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/google-java-format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/ktfmt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "automatic" 3 | } -------------------------------------------------------------------------------- /HOW TO USE.md: -------------------------------------------------------------------------------- 1 | # How to Use DocuBox 2 | 3 | ![](media/graphicC.png) 4 | 5 | ## Authentication Screens 6 | 7 | - You can either login if you have an account or create a new one. 8 | - DocuBox currently supports only Email based authentication with support of Google Login to be added in future. 9 | 10 |
11 | 12 | ![](media/graphicA.png) 13 | 14 | ## Home Screen 15 | - You can click on the Search Bar and search for files. 16 | - Clicking any of the items like Video, Images and rest will show you all the videos, images etc you have stored in DocuBox. 17 | - Quick Actions redirect you to the downloads folder where you downloaded files are stored and redirect you to important app pages. 18 | 19 | ## Documents Screen 20 | - You can click on any folder to navigate inside that folder. 21 | - You can click on any file to view it. Files supported for viewing currently are images, videos and audio. 22 | - You can click on the bottom + button to either create a new folder or upload a file. 23 | - You can long press on any folder to rename or delete it. 24 | - You can long press on any file to rename, give view access to other user, revoke access or delete the file. 25 | 26 |
27 | 28 | ![](media/graphicB.png) 29 | 30 | ## Share Screen 31 | - You can see all the files that have been shared with you. 32 | - You can also see all the files which you have shared with someone else. 33 | 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Vaibhav Jaiswal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/main/java/com/docubox/util/Secrets.kt 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'dagger.hilt.android.plugin' 6 | id 'androidx.navigation.safeargs' 7 | id 'kotlin-android-extensions' 8 | } 9 | 10 | apply plugin: 'kotlin-android' 11 | 12 | android { 13 | compileSdk 32 14 | 15 | defaultConfig { 16 | applicationId "com.docubox" 17 | minSdk 26 18 | targetSdk 32 19 | versionCode 1 20 | versionName "1.0" 21 | 22 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 23 | } 24 | 25 | buildFeatures { 26 | viewBinding true 27 | dataBinding true 28 | } 29 | 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | kotlinOptions { 41 | jvmTarget = '1.8' 42 | } 43 | } 44 | 45 | dependencies { 46 | 47 | implementation 'androidx.core:core-ktx:1.8.0' 48 | implementation 'androidx.appcompat:appcompat:1.4.2' 49 | implementation 'com.google.android.material:material:1.6.1' 50 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 51 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 52 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' 53 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' 54 | testImplementation 'junit:junit:4.13.2' 55 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 57 | implementation "androidx.core:core-ktx:1.8.0" 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | 60 | //Lifecycle 61 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" 62 | 63 | //Dagger Hilt 64 | implementation "com.google.dagger:hilt-android:$hilt_version" 65 | kapt "com.google.dagger:hilt-compiler:$hilt_version" 66 | 67 | //Navigation 68 | implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" 69 | implementation "androidx.navigation:navigation-ui-ktx:$nav_version" 70 | 71 | //DataStore 72 | implementation "androidx.datastore:datastore-preferences:1.0.0" 73 | 74 | //retrofit 75 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 76 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0' 77 | implementation 'com.squareup.okhttp3:logging-interceptor:4.4.1' 78 | 79 | //Utility 80 | implementation 'com.google.code.gson:gson:2.8.9' 81 | implementation 'com.jakewharton.timber:timber:5.0.1' 82 | implementation 'com.guolindev.permissionx:permissionx:1.6.4' 83 | implementation "net.gotev:uploadservice:4.7.0" 84 | implementation 'com.airbnb.android:lottie:5.2.0' 85 | 86 | //testing 87 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0" 88 | testImplementation "com.google.truth:truth:1.1.3" 89 | 90 | 91 | } -------------------------------------------------------------------------------- /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/release/docubox.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/release/docubox.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.docubox", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "attributes": [], 14 | "versionCode": 1, 15 | "versionName": "1.0", 16 | "outputFile": "app-release.apk" 17 | } 18 | ], 19 | "elementType": "File" 20 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/docubox/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.docubox", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/ic_logo-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/ic_logo-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/MyApp.kt: -------------------------------------------------------------------------------- 1 | package com.docubox 2 | 3 | import android.app.Application 4 | import com.docubox.util.NotificationHelper 5 | import dagger.hilt.android.HiltAndroidApp 6 | import net.gotev.uploadservice.UploadServiceConfig 7 | import timber.log.Timber 8 | 9 | // Application class of our app which will be used by dagger hilt for dependency injection 10 | @HiltAndroidApp 11 | class MyApp : Application() { 12 | 13 | private lateinit var notificationHelper: NotificationHelper 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | Timber.plant(Timber.DebugTree()) // For debugging via logcat messages 18 | notificationHelper = NotificationHelper(this) 19 | notificationHelper.setNotificationChannel() 20 | UploadServiceConfig.initialize( 21 | this, NotificationHelper.CHANNEL_ID, true 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/StorageCache.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | import java.util.* 5 | 6 | data class CacheData( 7 | val items: List, 8 | val directory: String?, 9 | val folderName: String, 10 | ) 11 | 12 | object StorageCache { 13 | 14 | private val cached = Stack() 15 | private val cachedDirectories = Stack() 16 | 17 | fun addToCache(data: CacheData) { 18 | cached.push(data) 19 | } 20 | 21 | fun popCache(): CacheData = cached.pop() 22 | 23 | fun isEmpty() = cached.empty() 24 | 25 | fun clearCache() = cached.clear() 26 | 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/dataSources/dataStore/DataStoreManager.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.dataSources.dataStore 2 | 3 | import androidx.annotation.WorkerThread 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import com.docubox.util.extensions.get 7 | import com.docubox.util.extensions.set 8 | import kotlin.properties.ReadWriteProperty 9 | import kotlin.reflect.KProperty 10 | 11 | // A wrapper class to store data in data store 12 | class DataStoreManager( 13 | private val dataStore: DataStore, 14 | private val key: Preferences.Key, 15 | private val defaultValue: T 16 | ) : ReadWriteProperty { 17 | 18 | // A special thread for performing datastore operations 19 | @WorkerThread 20 | override fun getValue(thisRef: Any, property: KProperty<*>): T = 21 | dataStore.get(key, defaultValue) 22 | 23 | override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) { 24 | dataStore.set(key, value) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/dataSources/dataStore/PreferencesManager.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.dataSources.dataStore 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.datastore.preferences.core.booleanPreferencesKey 6 | import androidx.datastore.preferences.core.stringPreferencesKey 7 | import com.docubox.data.local.dataSources.dataStore.Keys.ON_BOARDING_KEY 8 | import com.docubox.data.local.dataSources.dataStore.Keys.USER_KEY 9 | import kotlinx.coroutines.flow.map 10 | import javax.inject.Inject 11 | import javax.inject.Singleton 12 | 13 | // An object to store data store configurations 14 | private object Keys { 15 | val USER_KEY = stringPreferencesKey("user") 16 | val ON_BOARDING_KEY = booleanPreferencesKey("OnBoarding") 17 | } 18 | 19 | // Class to handle data store 20 | @Singleton 21 | class PreferencesManager @Inject constructor(private val dataStore: DataStore) { 22 | 23 | // Assign the user by putting details in the DataStoreManager.kt wrapper class 24 | var user: String? by DataStoreManager( 25 | dataStore = dataStore, 26 | key = USER_KEY, 27 | defaultValue = "" 28 | ) 29 | 30 | var onBoarding: Boolean? by DataStoreManager( 31 | dataStore = dataStore, 32 | key = ON_BOARDING_KEY, 33 | defaultValue = false 34 | ) 35 | 36 | // Get the data of that user in datastore 37 | fun observeUser() = dataStore.data.map { it[USER_KEY] } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/models/FileOption.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.models 2 | 3 | import androidx.annotation.DrawableRes 4 | 5 | sealed class FileOption(val text: String, @DrawableRes val icon: Int = 0) { 6 | object Download : FileOption("Download") 7 | object Rename : FileOption("Rename") 8 | object Share : FileOption("Share Access") 9 | object RevokeShare : FileOption("Revoke Share Access") 10 | object Delete : FileOption("Delete File") 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/models/FileType.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.models 2 | 3 | import androidx.annotation.DrawableRes 4 | import com.docubox.R 5 | 6 | // Class to get media type of a file 7 | sealed class FileType( 8 | val type: String, 9 | @DrawableRes val icon: Int, 10 | val mimeType: String, 11 | val title: String = mimeType 12 | ) { 13 | object Audio : FileType("Audio", R.drawable.ic_audio, "audio", "Audio") 14 | object Document : FileType("Documents", R.drawable.ic_document, "application/pdf", "Documents") 15 | object Image : FileType("Image", R.drawable.ic_image, "image", "Images") 16 | object Video : FileType("Video", R.drawable.ic_video, "video", "Videos") 17 | object File : FileType("File", R.drawable.ic_document, "application", "Documents") 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/models/FolderOptions.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.models 2 | 3 | import androidx.annotation.DrawableRes 4 | 5 | sealed class FolderOptions(val text: String, @DrawableRes val icon: Int = 0) { 6 | object Rename : FolderOptions("Rename") 7 | object Share : FolderOptions("Share Access") 8 | object RevokeShare : FolderOptions("Revoke Share Access") 9 | object Delete : FolderOptions("Delete Folder") 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/models/SearchResult.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.models 2 | 3 | import java.io.Serializable 4 | 5 | data class SearchResult( 6 | val results: List 7 | ) : Serializable 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/models/StorageItem.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.models 2 | 3 | import androidx.annotation.DrawableRes 4 | import com.docubox.R 5 | import com.docubox.data.remote.models.responses.file.FileDto 6 | import com.docubox.data.remote.models.responses.folder.FolderDto 7 | import com.docubox.util.extensions.getFileType 8 | import java.io.Serializable 9 | 10 | // Class to represent a single storage item in app (Eg. a file or a folder) 11 | sealed class StorageItem( 12 | open val id: String, 13 | open val name: String, 14 | open val description: String, 15 | @DrawableRes open val icon: Int 16 | ) : Serializable { 17 | data class File( 18 | val file: FileDto, 19 | val fileType: FileType = file.fileType.getFileType() 20 | ) : StorageItem(file.id, file.fileName, file.fileSize, fileType.icon) 21 | 22 | data class Folder( 23 | val folder: FolderDto 24 | ) : StorageItem(folder.id, folder.folderName, "", R.drawable.ic_folder) 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/local/models/User.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.models 2 | 3 | import java.io.Serializable 4 | 5 | // Class to represent a user in our app 6 | data class User( 7 | val id: String = "", // User Id 8 | val token: String = "", // Authentication token 9 | val userEmail: String = "", 10 | val userName: String = "", 11 | val rootDirectory: String = "" 12 | ) : Serializable 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/mapper/FileMapper.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.mapper 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | import com.docubox.data.remote.models.responses.file.FileDto 5 | import javax.inject.Inject 6 | 7 | fun StorageItem.File.toRemote() = FileDto( 8 | fileDirectory = this.file.fileDirectory, 9 | fileName = this.file.fileName, 10 | // fileOwner = local.file.fileOwner, 11 | fileType = this.file.fileType, 12 | fileSize = this.file.fileSize, 13 | fileStorageUrl = this.file.fileStorageUrl, 14 | id = this.file.id, 15 | v = this.file.v, 16 | ) 17 | 18 | fun FileDto.toLocal(): StorageItem.File = StorageItem.File( 19 | file = this 20 | ) 21 | 22 | fun List.toRemote(): List = this.map { 23 | it.toRemote() 24 | } 25 | 26 | fun List.toLocal(): List = this.map { 27 | it.toLocal() 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/mapper/FolderMapper.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.mapper 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | import com.docubox.data.remote.models.responses.folder.FolderDto 5 | import javax.inject.Inject 6 | 7 | fun StorageItem.Folder.toRemote(): FolderDto = FolderDto( 8 | folderName = this.folder.folderName, 9 | folderOwner = this.folder.folderOwner, 10 | // folderParentDirectory = local.folder.folderParentDirectory, 11 | id = this.folder.id, 12 | v = this.folder.v, 13 | ) 14 | 15 | fun FolderDto.toLocal(): StorageItem.Folder = StorageItem.Folder(this) 16 | 17 | fun List.toRemote(): List = 18 | this.map { it.toRemote() } 19 | 20 | fun List.toLocal(): List = 21 | this.map { it.toLocal() } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/mapper/UserMapper.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.mapper 2 | 3 | import com.docubox.data.local.models.User 4 | import com.docubox.data.remote.models.UserDto 5 | import javax.inject.Inject 6 | 7 | // Class to map single and multiple users from local data to remote dto (data transfer object) and vice versa 8 | fun User.toRemote(): UserDto = UserDto( 9 | id = this.id, 10 | userEmail = this.userEmail, 11 | userName = this.userName, 12 | token = this.token, 13 | rootDirectory = listOf(this.rootDirectory) 14 | ) 15 | 16 | fun UserDto.toLocal(): User = User( 17 | id = this.id, 18 | userEmail = this.userEmail, 19 | userName = this.userName, 20 | token = this.token, 21 | rootDirectory = this.rootDirectory.getOrNull(0) ?: "" 22 | ) 23 | 24 | fun List.toRemote(): List = this.map { it.toRemote() } 25 | 26 | fun List.toLocal(): List = this.map { it.toLocal() } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/api/AuthService.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.api 2 | 3 | import com.docubox.data.remote.models.UserDto 4 | import com.docubox.data.remote.models.requests.LoginRequest 5 | import com.docubox.data.remote.models.requests.RegisterRequest 6 | import retrofit2.Response 7 | import retrofit2.http.Body 8 | import retrofit2.http.POST 9 | 10 | // An interface to provide authentication functions for logging in and signing up a user 11 | interface AuthService { 12 | 13 | @POST("auth/login") // API endpoints are specified using Retrofit's @POST annotation 14 | suspend fun loginUser( 15 | @Body request: LoginRequest // Attach LoginRequest as a parameter 16 | ): Response // Return a retrofit Response object with UserDto 17 | 18 | @POST("auth/signup") 19 | suspend fun registerUser( 20 | @Body request: RegisterRequest 21 | ): Response 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/api/StorageService.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.api 2 | 3 | import com.docubox.data.remote.models.MessageResponse 4 | import com.docubox.data.remote.models.responses.StorageConsumption 5 | import com.docubox.data.remote.models.responses.file.FileListResponse 6 | import com.docubox.data.remote.models.responses.folder.CreateFolderResponse 7 | import com.docubox.data.remote.models.responses.folder.GetFolderResponse 8 | import com.docubox.data.remote.models.requests.CreateFolderRequest 9 | import com.docubox.data.remote.models.requests.GetFileRequest 10 | import com.docubox.data.remote.models.requests.GetFolderRequest 11 | import com.docubox.data.remote.models.requests.RenameFileRequest 12 | import com.docubox.data.remote.models.requests.RenameFolderRequest 13 | import com.docubox.data.remote.models.requests.RevokeFileRequest 14 | import com.docubox.data.remote.models.requests.ShareFileRequest 15 | import retrofit2.Response 16 | import retrofit2.http.Body 17 | import retrofit2.http.Header 18 | import retrofit2.http.POST 19 | 20 | interface StorageService { 21 | 22 | @POST("documents/get-folders-in-folder") 23 | suspend fun getFolders( 24 | @Body body: GetFolderRequest, 25 | @Header("Authorization") token: String 26 | ): Response 27 | 28 | @POST("documents/get-files-in-folder") 29 | suspend fun getFiles( 30 | @Body body: GetFileRequest, 31 | @Header("Authorization") token: String 32 | ): Response 33 | 34 | @POST("documents/create-folder") 35 | suspend fun createFolder( 36 | @Body body: CreateFolderRequest, 37 | @Header("Authorization") token: String 38 | ): Response 39 | 40 | @POST("documents/get-files-shared-to-me") 41 | suspend fun getFilesSharedToMe( 42 | @Header("Authorization") token: String 43 | ): Response 44 | 45 | @POST("documents/get-files-shared-by-me") 46 | suspend fun getFilesSharedByMe( 47 | @Header("Authorization") token: String 48 | ): Response 49 | 50 | @POST("documents/share-file") 51 | suspend fun shareFile( 52 | @Body body: ShareFileRequest, 53 | @Header("Authorization") token: String 54 | ): Response 55 | 56 | @POST("documents/revoke-file") 57 | suspend fun revokeFile( 58 | @Body body: RevokeFileRequest, 59 | @Header("Authorization") token: String 60 | ): Response 61 | 62 | @POST("documents/delete-file") 63 | suspend fun deleteFile( 64 | @Body body: Map, 65 | @Header("Authorization") token: String 66 | ): Response 67 | 68 | @POST("documents/delete-folder") 69 | suspend fun deleteFolder( 70 | @Body body: Map, 71 | @Header("Authorization") token: String 72 | ): Response 73 | 74 | @POST("documents/storage-consumption") 75 | suspend fun getStorageConsumption( 76 | @Header("Authorization") token: String 77 | ): Response 78 | 79 | @POST("documents/search-file-name") 80 | suspend fun searchFilesByName( 81 | @Body body: Map, 82 | @Header("Authorization") token: String 83 | ): Response 84 | 85 | @POST("documents/search-file-type") 86 | suspend fun searchFilesByType( 87 | @Body body: Map, 88 | @Header("Authorization") token: String 89 | ): Response 90 | 91 | @POST("documents/rename-file") 92 | suspend fun renameFile( 93 | @Body body: RenameFileRequest, 94 | @Header("Authorization") token: String 95 | ): Response 96 | 97 | @POST("documents/rename-folder") 98 | suspend fun renameFolder( 99 | @Body body: RenameFolderRequest, 100 | @Header("Authorization") token: String 101 | ): Response 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/dataSources/AuthDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.dataSources 2 | 3 | import com.docubox.data.remote.models.UserDto 4 | import com.docubox.data.remote.models.requests.LoginRequest 5 | import com.docubox.data.remote.models.requests.RegisterRequest 6 | import com.docubox.data.remote.api.AuthService 7 | import com.docubox.util.Resource 8 | import com.docubox.util.safeApiCall 9 | import javax.inject.Inject 10 | 11 | // Class to login and signup user by implementing AuthService interface 12 | class AuthDataSource @Inject constructor(private val authService: AuthService) { 13 | 14 | suspend fun loginUser( 15 | email: String, 16 | password: String 17 | ): Resource = safeApiCall { 18 | authService.loginUser(LoginRequest(email, password)) 19 | } 20 | 21 | suspend fun registerUser( 22 | username: String, 23 | email: String, 24 | password: String 25 | ): Resource = safeApiCall { 26 | authService.registerUser(RegisterRequest(email, username, password)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/MessageResponse.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class MessageResponse( 6 | @SerializedName("message") 7 | val message: String = "" 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/UserDto.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | // User Data Transfer Object Class for representing structure of a User received from API 6 | data class UserDto( 7 | @SerializedName("_id") 8 | val id: String = "", 9 | @SerializedName("token") 10 | val token: String = "", 11 | @SerializedName("userEmail") 12 | val userEmail: String = "", 13 | @SerializedName("userName") 14 | val userName: String = "", 15 | @SerializedName("userDataFolder") 16 | val rootDirectory: List = emptyList() 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/CreateFolderRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class CreateFolderRequest( 7 | @SerializedName("folderName") 8 | val folderName: String = "", 9 | @SerializedName("folderParentDirectory") 10 | val folderParentDirectory: String = "" 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/GetFileRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GetFileRequest( 7 | @SerializedName("fileDirectory") 8 | val fileDirectory: String? = "" 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/GetFolderRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GetFolderRequest( 7 | @SerializedName("folderParentDirectory") 8 | val folderParentDirectory: String? = "" 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/LoginRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | // API request to login a user 6 | data class LoginRequest( 7 | @SerializedName("userEmail") 8 | val userEmail: String = "", 9 | @SerializedName("userPassword") 10 | val userPassword: String = "" 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/RegisterRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | // API request to signup a user 6 | data class RegisterRequest( 7 | @SerializedName("userEmail") 8 | val userEmail: String = "", 9 | @SerializedName("userName") 10 | val userName: String = "", 11 | @SerializedName("userPassword") 12 | val userPassword: String = "" 13 | ) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/RenameFileRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class RenameFileRequest( 6 | @SerializedName("fileId") 7 | val fileId: String, 8 | @SerializedName("newName") 9 | val newFileName: String, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/RenameFolderRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class RenameFolderRequest( 6 | @SerializedName("folderId") 7 | val folderId: String, 8 | @SerializedName("newName") 9 | val newFileName: String, 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/RevokeFileRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class RevokeFileRequest( 6 | @SerializedName("fileId") 7 | val fileId: String, 8 | @SerializedName("userToRevokeEmail") 9 | val email: String 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/requests/ShareFileRequest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.requests 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ShareFileRequest( 6 | @SerializedName("fileId") 7 | val fileId: String, 8 | @SerializedName("userToShareEmail") 9 | val email: String 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/OwnerDto.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class OwnerDto( 6 | @SerializedName("_id") 7 | val id: String = "", 8 | @SerializedName("userEmail") 9 | val userEmail: String = "", 10 | @SerializedName("userName") 11 | val userName: String = "", 12 | @SerializedName("__v") 13 | val v: Int = 0 14 | ) 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/ParentDirectory.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class ParentDirectory( 6 | @SerializedName("folderName") 7 | val folderName: String = "", 8 | @SerializedName("folderOwner") 9 | val folderOwner: String = "", 10 | @SerializedName("folderParentDirectory") 11 | val folderParentDirectory: String = "", 12 | @SerializedName("_id") 13 | val id: String = "", 14 | @SerializedName("__v") 15 | val v: Int = 0 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/StorageConsumption.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class StorageConsumption( 7 | @SerializedName("storageConsumption") 8 | val storageConsumption: String = "", 9 | @SerializedName("totalStorage") 10 | val totalStorage: String = "50" 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/file/FileDto.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.file 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | import java.io.Serializable 6 | 7 | data class FileDto( 8 | @SerializedName("fileDirectory") 9 | val fileDirectory: List = emptyList(), 10 | @SerializedName("fileName") 11 | val fileName: String = "", 12 | // @SerializedName("fileOwner") 13 | // val fileOwner: OwnerDto = OwnerDto(), 14 | @SerializedName("fileSize") 15 | val fileSize: String = "", 16 | @SerializedName("fileStorageUrl") 17 | val fileStorageUrl: String = "", 18 | @SerializedName("fileType") 19 | val fileType: String = "", 20 | @SerializedName("_id") 21 | val id: String = "", 22 | @SerializedName("__v") 23 | val v: Int = 0, 24 | @SerializedName("fileSharedTo") 25 | val fileSharedTo: List = emptyList() 26 | 27 | ) : Serializable -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/file/FileListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.file 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | import java.io.Serializable 6 | 7 | data class FileListResponse( 8 | @SerializedName("fileList") 9 | val fileList: List = listOf() 10 | ) : Serializable -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/file/UploadFileResponse.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.file 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class UploadFileResponse( 6 | @SerializedName("file") 7 | val `file`: UploadedFile = UploadedFile() 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/file/UploadedFile.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.file 2 | 3 | import com.docubox.data.remote.models.responses.OwnerDto 4 | import com.docubox.data.remote.models.responses.ParentDirectory 5 | import com.google.gson.annotations.SerializedName 6 | 7 | data class UploadedFile( 8 | @SerializedName("fileDirectory") 9 | val fileDirectory: ParentDirectory = ParentDirectory(), 10 | @SerializedName("fileName") 11 | val fileName: String = "", 12 | @SerializedName("fileOwner") 13 | val fileOwner: OwnerDto = OwnerDto(), 14 | @SerializedName("fileSize") 15 | val fileSize: String = "", 16 | @SerializedName("fileStorageUrl") 17 | val fileStorageUrl: String = "", 18 | @SerializedName("fileType") 19 | val fileType: String = "", 20 | @SerializedName("_id") 21 | val id: String = "", 22 | @SerializedName("__v") 23 | val v: Int = 0 24 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/folder/CreateFolderResponse.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.folder 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class CreateFolderResponse( 7 | @SerializedName("folder") 8 | val folder: FolderDto = FolderDto() 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/folder/FolderDto.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.folder 2 | 3 | import com.docubox.data.remote.models.responses.OwnerDto 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class FolderDto( 7 | @SerializedName("folderName") 8 | val folderName: String = "", 9 | @SerializedName("folderOwner") 10 | val folderOwner: OwnerDto = OwnerDto(), 11 | // @SerializedName("folderParentDirectory") 12 | // val folderParentDirectory: ParentDirectory = ParentDirectory(), 13 | @SerializedName("_id") 14 | val id: String = "", 15 | @SerializedName("__v") 16 | val v: Int = 0 17 | ) 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/remote/models/responses/folder/GetFolderResponse.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.remote.models.responses.folder 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class GetFolderResponse( 6 | @SerializedName("folderList") 7 | val folderList: List = listOf() 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/repo/AuthRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.repo 2 | 3 | import com.docubox.data.mapper.toLocal 4 | import com.docubox.data.remote.dataSources.AuthDataSource 5 | import com.docubox.data.remote.models.UserDto 6 | import com.docubox.domain.repo.AuthRepo 7 | import com.docubox.domain.repo.PreferenceRepo 8 | import com.docubox.util.Resource 9 | import com.docubox.util.extensions.mapToUnit 10 | import kotlinx.coroutines.flow.flow 11 | import javax.inject.Inject 12 | 13 | // Repository for all authentication functions 14 | class AuthRepoImpl @Inject constructor( 15 | private val authDataSource: AuthDataSource, // Class to login and signup user 16 | private val preferenceRepo: PreferenceRepo, // Repository that contains data store functions 17 | ): AuthRepo { 18 | 19 | override fun isUserLoggedIn() = preferenceRepo.isUserLoggedIn() 20 | 21 | override suspend fun loginUser( 22 | email: String, 23 | password: String 24 | ) = flow { 25 | // Use resource->Resource.Success to get resource state and resource.data to get resource data 26 | emit(Resource.Loading()) 27 | val resource = authDataSource.loginUser(email, password) 28 | if (resource is Resource.Success) 29 | saveUserLocally(resource.data) 30 | emit(resource.mapToUnit()) 31 | } 32 | 33 | override suspend fun registerUser( 34 | username: String, 35 | email: String, 36 | password: String 37 | ) = flow { 38 | emit(Resource.Loading()) 39 | val resource = authDataSource.registerUser(username, email, password) 40 | if (resource is Resource.Success) 41 | saveUserLocally(resource.data) 42 | emit(resource.mapToUnit()) 43 | } 44 | 45 | override suspend fun logoutUser() { 46 | preferenceRepo.removeUser() 47 | } 48 | 49 | private suspend fun saveUserLocally(userDto: UserDto?) { 50 | userDto?.let { preferenceRepo.saveUser(it.toLocal()) } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/data/repo/PreferencesRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.repo 2 | 3 | import com.docubox.data.local.dataSources.dataStore.PreferencesManager 4 | import com.docubox.data.local.models.User 5 | import com.docubox.domain.repo.PreferenceRepo 6 | import com.google.gson.Gson 7 | import kotlinx.coroutines.flow.map 8 | import javax.inject.Inject 9 | 10 | // Repository for all data store functions 11 | class PreferencesRepoImpl @Inject constructor(private val dataStore: PreferencesManager): PreferenceRepo { 12 | 13 | override fun saveUser(user: User) { 14 | val serializedUser = Gson().toJson(user) // Convert User object to json 15 | dataStore.user = serializedUser 16 | } 17 | 18 | override fun getUser(): User? { 19 | return Gson().fromJson(dataStore.user, User::class.java) // Convert user json to User object 20 | } 21 | 22 | override fun removeUser() { 23 | dataStore.user = null 24 | } 25 | 26 | override fun observeUser() = dataStore.observeUser().map { Gson().fromJson(it, User::class.java) } 27 | 28 | override fun isUserLoggedIn() = getUser() != null 29 | 30 | override fun getUserToken() = getUser()?.token 31 | 32 | override fun isOnBoardingComplete() = dataStore.onBoarding == true 33 | 34 | override fun setOnBoardingComplete() { 35 | dataStore.onBoarding = null 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/di/LocalModule.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.di 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler 6 | import androidx.datastore.preferences.SharedPreferencesMigration 7 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory 8 | import androidx.datastore.preferences.core.Preferences 9 | import androidx.datastore.preferences.core.emptyPreferences 10 | import androidx.datastore.preferences.preferencesDataStoreFile 11 | import com.docubox.data.local.dataSources.dataStore.PreferencesManager 12 | import com.docubox.data.remote.dataSources.AuthDataSource 13 | import com.docubox.data.remote.dataSources.StorageDataSource 14 | import com.docubox.data.repo.AuthRepoImpl 15 | import com.docubox.data.repo.PreferencesRepoImpl 16 | import com.docubox.data.repo.StorageRepoImpl 17 | import com.docubox.domain.repo.AuthRepo 18 | import com.docubox.domain.repo.PreferenceRepo 19 | import com.docubox.domain.repo.StorageRepo 20 | import com.docubox.util.Constants.DATASTORE_NAME 21 | import dagger.Module 22 | import dagger.Provides 23 | import dagger.hilt.InstallIn 24 | import dagger.hilt.android.qualifiers.ApplicationContext 25 | import dagger.hilt.components.SingletonComponent 26 | import kotlinx.coroutines.CoroutineScope 27 | import kotlinx.coroutines.Dispatchers 28 | import kotlinx.coroutines.SupervisorJob 29 | import javax.inject.Singleton 30 | 31 | // Module for providing local data store instance 32 | @Module 33 | @InstallIn(SingletonComponent::class) 34 | object LocalModule { 35 | 36 | @Provides 37 | @Singleton 38 | fun providesDataStore(@ApplicationContext context: Context): DataStore = 39 | PreferenceDataStoreFactory.create( 40 | corruptionHandler = ReplaceFileCorruptionHandler(produceNewData = { emptyPreferences() }), 41 | migrations = listOf(SharedPreferencesMigration(context, DATASTORE_NAME)), 42 | scope = CoroutineScope(Dispatchers.IO + SupervisorJob()), 43 | produceFile = { context.preferencesDataStoreFile(DATASTORE_NAME) } 44 | ) 45 | 46 | @Provides 47 | @Singleton 48 | fun providesAuthRepo( 49 | authDataSource: AuthDataSource, 50 | preferencesRepo: PreferenceRepo 51 | ): AuthRepo = 52 | AuthRepoImpl(authDataSource, preferencesRepo) 53 | 54 | @Provides 55 | @Singleton 56 | fun providesPreferenceRepo( 57 | preferencesManager: PreferencesManager 58 | ): PreferenceRepo = 59 | PreferencesRepoImpl(preferencesManager) 60 | 61 | @Provides 62 | @Singleton 63 | fun providesStorageRepo( 64 | storageDataSource: StorageDataSource, 65 | preferencesRepo: PreferenceRepo 66 | ): StorageRepo = 67 | StorageRepoImpl(storageDataSource, preferencesRepo) 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.di 2 | 3 | import com.docubox.data.remote.api.AuthService 4 | import com.docubox.data.remote.api.StorageService 5 | import com.docubox.util.Secrets.BASE_URL 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import okhttp3.OkHttpClient 11 | import okhttp3.logging.HttpLoggingInterceptor 12 | import retrofit2.Retrofit 13 | import retrofit2.converter.gson.GsonConverterFactory 14 | import javax.inject.Singleton 15 | 16 | // Module for providing network functions like Retrofit Instance, Auth service and OkHttpClient 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | object NetworkModule { 20 | 21 | @Singleton 22 | @Provides 23 | fun providesClient(): OkHttpClient { 24 | val loggingInterceptor = HttpLoggingInterceptor() 25 | loggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC 26 | return OkHttpClient.Builder().addInterceptor(loggingInterceptor).build() 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | fun providesRetrofit(client: OkHttpClient): Retrofit = Retrofit 32 | .Builder() 33 | .baseUrl(BASE_URL) 34 | .client(client) 35 | .addConverterFactory(GsonConverterFactory.create()) 36 | .build() 37 | 38 | @Provides 39 | @Singleton 40 | fun providesAuthService(retrofit: Retrofit): AuthService = 41 | retrofit.create(AuthService::class.java) 42 | 43 | @Provides 44 | @Singleton 45 | fun providesStorageService(retrofit: Retrofit): StorageService = 46 | retrofit.create(StorageService::class.java) 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/domain/repo/AuthRepo.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.domain.repo 2 | 3 | import com.docubox.util.Resource 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface AuthRepo { 7 | 8 | fun isUserLoggedIn(): Boolean 9 | 10 | suspend fun loginUser( 11 | email: String, 12 | password: String 13 | ): Flow> 14 | 15 | suspend fun registerUser( 16 | username: String, 17 | email: String, 18 | password: String 19 | ): Flow> 20 | 21 | suspend fun logoutUser() 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/domain/repo/PreferenceRepo.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.domain.repo 2 | 3 | import com.docubox.data.local.models.User 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface PreferenceRepo { 7 | 8 | fun saveUser(user: User) 9 | 10 | fun getUser(): User? 11 | 12 | fun removeUser() 13 | 14 | fun observeUser(): Flow 15 | 16 | fun isUserLoggedIn(): Boolean 17 | 18 | fun getUserToken(): String? 19 | 20 | fun isOnBoardingComplete(): Boolean 21 | 22 | fun setOnBoardingComplete() 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/domain/repo/StorageRepo.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.domain.repo 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | import com.docubox.data.remote.models.MessageResponse 5 | import com.docubox.data.remote.models.responses.StorageConsumption 6 | import com.docubox.util.Resource 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | interface StorageRepo { 10 | 11 | suspend fun getAllFiles(fileDirectory: String?): Flow>> 12 | 13 | suspend fun getAllFolders(folderParentDirectory: String?): Flow>> 14 | 15 | suspend fun createFolder(folderName: String, folderDirectory: String): Flow> 16 | 17 | suspend fun getFilesSharedByMe(): Flow>> 18 | 19 | suspend fun getFilesSharedToMe(): Flow>> 20 | 21 | suspend fun shareFile(fileId: String, email: String): Flow> 22 | 23 | suspend fun revokeShareFile(fileId: String, email: String): Flow> 24 | 25 | suspend fun deleteFile(fileId: String): Flow> 26 | 27 | suspend fun deleteFolder(folderId: String): Flow> 28 | 29 | suspend fun getStorageConsumption(): Flow> 30 | 31 | suspend fun searchFileByQuery(query: String): Flow>> 32 | 33 | suspend fun searchFileByType(type: String): Flow>> 34 | 35 | suspend fun downloadFile(file: StorageItem.File) 36 | 37 | suspend fun renameFile( 38 | file: StorageItem.File, 39 | newName: String 40 | ): Flow> 41 | 42 | suspend fun renameFolder( 43 | folder: StorageItem.Folder, 44 | newName: String 45 | ): Flow> 46 | 47 | suspend fun getStorageConsumptionValue(): StorageConsumption? 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/service/FileUploadService.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.service 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Binder 7 | import android.os.IBinder 8 | import androidx.lifecycle.LifecycleOwner 9 | import androidx.lifecycle.LifecycleService 10 | import com.docubox.util.FileUtil 11 | import com.docubox.util.Secrets.BASE_URL 12 | import com.docubox.util.extensions.showToast 13 | import net.gotev.uploadservice.data.UploadInfo 14 | import net.gotev.uploadservice.network.ServerResponse 15 | import net.gotev.uploadservice.observer.request.RequestObserverDelegate 16 | import net.gotev.uploadservice.protocols.multipart.MultipartUploadRequest 17 | import timber.log.Timber 18 | import kotlin.coroutines.resume 19 | import kotlin.coroutines.suspendCoroutine 20 | 21 | class FileUploadService : LifecycleService() { 22 | 23 | companion object { 24 | private val UPLOAD_FILE_URL = BASE_URL + "documents/create-file" 25 | } 26 | 27 | private val binder = FileUploadBinder() 28 | 29 | inner class FileUploadBinder : Binder() { 30 | fun getService() = this@FileUploadService 31 | } 32 | 33 | override fun onBind(intent: Intent): IBinder { 34 | super.onBind(intent) 35 | return binder 36 | } 37 | 38 | suspend fun uploadFile( 39 | file: Uri, 40 | fileDirectory: String?, 41 | storageLeft: Float, 42 | token: String, 43 | lifeCycleOwner: LifecycleOwner 44 | ) = suspendCoroutine { 45 | val fileSize = FileUtil.getFileSize(this, file) 46 | if (fileSize > storageLeft) { 47 | showToast("Unable to upload file, you have crossed your file storage limit") 48 | it.resume(false) 49 | stopSelf() 50 | return@suspendCoroutine 51 | } 52 | val directory = fileDirectory?.let { dir -> listOf(dir) } ?: emptyList() 53 | MultipartUploadRequest(this@FileUploadService, UPLOAD_FILE_URL) 54 | .setMethod("POST") 55 | .addFileToUpload(filePath = file.toString(), parameterName = "upload") 56 | .addArrayParameter("fileDirectory", directory) 57 | .setBearerAuth(token) 58 | .subscribe( 59 | this@FileUploadService, 60 | lifeCycleOwner, 61 | object : RequestObserverDelegate { 62 | override fun onCompleted(context: Context, uploadInfo: UploadInfo) = Unit 63 | 64 | override fun onCompletedWhileNotObserving() = Unit 65 | 66 | override fun onError( 67 | context: Context, 68 | uploadInfo: UploadInfo, 69 | exception: Throwable 70 | ) { 71 | Timber.d("DOCUBOX_ERROR: ${exception.message}") 72 | it.resume(false) 73 | } 74 | 75 | override fun onProgress(context: Context, uploadInfo: UploadInfo) = Unit 76 | 77 | override fun onSuccess( 78 | context: Context, 79 | uploadInfo: UploadInfo, 80 | serverResponse: ServerResponse 81 | ) { 82 | it.resume(true) 83 | } 84 | }) 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/adapter/AbstractAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import androidx.viewbinding.ViewBinding 9 | 10 | // An abstract adapter class for recyclerview 11 | abstract class AbstractAdapter( 12 | private val binding: (LayoutInflater, ViewGroup?, Boolean) -> VB 13 | ) : ListAdapter>(DiffUtilCallback()) { 14 | 15 | protected abstract fun onItemClick(item: ITEM, view: View) 16 | 17 | protected abstract fun VB.bind(item: ITEM, position: Int) 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 20 | val viewHolder = ViewHolder(binding(LayoutInflater.from(parent.context), parent, false)) 21 | viewHolder.binding.root.setOnClickListener { 22 | val pos = viewHolder.adapterPosition 23 | if (pos != RecyclerView.NO_POSITION) 24 | onItemClick(currentList[pos], it) 25 | } 26 | return viewHolder 27 | } 28 | 29 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 30 | holder.binding.bind(currentList[position], position) 31 | } 32 | 33 | class ViewHolder(val binding: VB) : RecyclerView.ViewHolder(binding.root) 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/adapter/DiffUtilCallback.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.adapter 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.recyclerview.widget.DiffUtil 5 | import com.docubox.data.local.models.StorageItem 6 | 7 | // Class for configuring DiffUtil 8 | internal class DiffUtilCallback : DiffUtil.ItemCallback() { 9 | 10 | override fun areItemsTheSame(oldItem: ITEM & Any, newItem: ITEM & Any): Boolean { 11 | return when { 12 | oldItem is StorageItem && newItem is StorageItem -> oldItem.id == newItem.id 13 | else -> oldItem === newItem 14 | } 15 | } 16 | 17 | @SuppressLint("DiffUtilEquals") 18 | override fun areContentsTheSame(oldItem: ITEM & Any, newItem: ITEM & Any): Boolean { 19 | return when { 20 | oldItem is StorageItem && newItem is StorageItem -> oldItem == newItem 21 | else -> oldItem.hashCode() == newItem.hashCode() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/adapter/OneAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.viewbinding.ViewBinding 7 | 8 | // Class for building an adapter using AbstractAdapter 9 | class OneAdapter( 10 | binding: (LayoutInflater, ViewGroup?, Boolean) -> VB, 11 | private val onBind: VB.(ITEM, Int) -> Unit, 12 | private val itemClick: ITEM.(View) -> Unit 13 | ) : AbstractAdapter(binding) { 14 | 15 | override fun onItemClick(item: ITEM, view: View) { 16 | itemClick(item, view) 17 | } 18 | 19 | override fun VB.bind(item: ITEM, position: Int) { 20 | onBind(item, position) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/AuthActivity.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.navigation.NavController 6 | import androidx.navigation.findNavController 7 | import com.docubox.R 8 | import com.docubox.databinding.ActivityAuthBinding 9 | import com.docubox.util.viewBinding.viewBinding 10 | import dagger.hilt.android.AndroidEntryPoint 11 | 12 | @AndroidEntryPoint 13 | class AuthActivity : AppCompatActivity() { 14 | 15 | private val binding by viewBinding(ActivityAuthBinding::inflate) 16 | private lateinit var navController: NavController 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(binding.root) 21 | navController = findNavController(R.id.authNavHost) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/gettingStarted/GettingStartedFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.gettingStarted 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.navigation.fragment.findNavController 7 | import com.docubox.R 8 | import com.docubox.databinding.FragmentGettingStartedBinding 9 | import com.docubox.util.extensions.singleClick 10 | import com.docubox.util.viewBinding.viewBinding 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | class GettingStartedFragment : Fragment(R.layout.fragment_getting_started) { 15 | 16 | private val binding by viewBinding(FragmentGettingStartedBinding::bind) 17 | 18 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 19 | super.onViewCreated(view, savedInstanceState) 20 | initListeners() 21 | } 22 | 23 | private fun initListeners() = with(binding) { 24 | loginBtn.singleClick { findNavController().navigate(R.id.action_gettingStartedFragment_to_loginFragment) } 25 | registerBtn.singleClick { findNavController().navigate(R.id.action_gettingStartedFragment_to_registerFragment) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/login/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.login 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.core.widget.doAfterTextChanged 6 | import androidx.fragment.app.Fragment 7 | import androidx.fragment.app.viewModels 8 | import androidx.navigation.fragment.findNavController 9 | import com.docubox.R 10 | import com.docubox.databinding.FragmentLoginBinding 11 | import com.docubox.ui.screens.main.DocuBoxActivity 12 | import com.docubox.util.extensions.* 13 | import com.docubox.util.viewBinding.viewBinding 14 | import dagger.hilt.android.AndroidEntryPoint 15 | 16 | @AndroidEntryPoint 17 | class LoginFragment : Fragment(R.layout.fragment_login) { 18 | 19 | private val binding by viewBinding(FragmentLoginBinding::bind) 20 | private val viewModel by viewModels() 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | initViews() 25 | initListeners() 26 | collectUIState() 27 | collectUiEvents() 28 | } 29 | 30 | private fun collectUiEvents() = viewModel.events.launchAndCollect(viewLifecycleOwner) { 31 | when (it) { 32 | LoginScreenEvents.NavigateToMainScreen -> requireActivity().navigate( 33 | DocuBoxActivity::class.java, 34 | true 35 | ) 36 | LoginScreenEvents.NavigateToRegisterScreen -> findNavController().navigate(R.id.action_loginFragment_to_registerFragment) 37 | is LoginScreenEvents.ShowToast -> requireContext().showToast(it.message) 38 | else -> {} 39 | } 40 | } 41 | 42 | private fun collectUIState() = viewModel.uiState.launchAndCollectLatest(viewLifecycleOwner) { 43 | // change progress bar visibility acc to isLoading 44 | with(binding) { 45 | emailTIL.error = it.emailError 46 | passwordTIL.error = it.passwordError 47 | emailTIET.isEnabled = it.areTextFieldsEnabled 48 | passwordTIET.isEnabled = it.areTextFieldsEnabled 49 | loginBtn.isEnabled = it.isLoginButtonEnabled 50 | progressLayout.visibleOrGone(it.isLoading) 51 | } 52 | } 53 | 54 | private fun initListeners() = with(binding) { 55 | emailTIET.doAfterTextChanged { viewModel.onEmailTextChange(it.toString()) } 56 | passwordTIET.doAfterTextChanged { viewModel.onPasswordTextChange(it.toString()) } 57 | loginBtn.singleClick(viewModel::onLoginButtonPressed) 58 | goToRegister.singleClick(viewModel::onGoToRegisterPress) 59 | } 60 | 61 | private fun initViews() = with(binding) {} 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/login/LoginScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.login 2 | 3 | sealed class LoginScreenEvents { 4 | data class ShowToast(val message: String) : LoginScreenEvents() 5 | object NavigateToMainScreen : LoginScreenEvents() 6 | object NavigateToRegisterScreen : LoginScreenEvents() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/login/LoginScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.login 2 | 3 | data class LoginScreenState( 4 | val email: String = "", 5 | val password: String = "", 6 | val isLoading: Boolean = false, 7 | 8 | val emailError: String? = null, 9 | val passwordError: String? = null 10 | ) { 11 | val isLoginButtonEnabled: Boolean 12 | get() = email.isNotBlank() && password.isNotBlank() && !isLoading 13 | 14 | val areTextFieldsEnabled: Boolean 15 | get() = !isLoading 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.login 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.docubox.domain.repo.AuthRepo 6 | import com.docubox.util.Resource 7 | import com.docubox.util.validateEmail 8 | import com.docubox.util.validatePassword 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import kotlinx.coroutines.flow.* 11 | import kotlinx.coroutines.launch 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class LoginViewModel @Inject constructor(private val authRepo: AuthRepo) : ViewModel() { 16 | 17 | private val _uiState = MutableStateFlow(LoginScreenState()) 18 | val uiState = _uiState.asStateFlow() 19 | 20 | private val _events = MutableSharedFlow() 21 | val events = _events.asSharedFlow() 22 | 23 | fun onEmailTextChange(email: String) = viewModelScope.launch { 24 | _uiState.update { it.copy(email = email.trim()) } 25 | } 26 | 27 | fun onPasswordTextChange(password: String) = viewModelScope.launch { 28 | _uiState.update { it.copy(password = password.trim()) } 29 | } 30 | 31 | private fun verifyUserInput(): Boolean { 32 | val emailError = uiState.value.email.validateEmail() 33 | val passwordError = uiState.value.password.validatePassword() 34 | _uiState.update { 35 | it.copy(emailError = emailError, passwordError = passwordError) 36 | } 37 | return emailError == null && passwordError == null 38 | } 39 | 40 | fun onLoginButtonPressed() = viewModelScope.launch { 41 | if (verifyUserInput()) 42 | loginUsingCredentials(uiState.value.email, uiState.value.password) 43 | } 44 | 45 | fun onGoToRegisterPress() = viewModelScope.launch { 46 | _events.emit(LoginScreenEvents.NavigateToRegisterScreen) 47 | } 48 | 49 | private suspend fun loginUsingCredentials(email: String, password: String) { 50 | authRepo.loginUser(email, password).collectLatest { 51 | _uiState.emit(uiState.value.copy(isLoading = it is Resource.Loading)) 52 | when (it) { 53 | is Resource.Error -> _events.emit(LoginScreenEvents.ShowToast(it.message)) 54 | is Resource.Loading -> Unit 55 | is Resource.Success -> _events.emit(LoginScreenEvents.NavigateToMainScreen) 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/register/RegisterFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.register 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.fragment.app.viewModels 7 | import androidx.navigation.fragment.findNavController 8 | import com.docubox.R 9 | import com.docubox.databinding.FragmentRegisterBinding 10 | import com.docubox.ui.screens.main.DocuBoxActivity 11 | import com.docubox.util.extensions.* 12 | import com.docubox.util.viewBinding.viewBinding 13 | import dagger.hilt.android.AndroidEntryPoint 14 | 15 | @AndroidEntryPoint 16 | class RegisterFragment : Fragment(R.layout.fragment_register) { 17 | 18 | private val binding by viewBinding(FragmentRegisterBinding::bind) 19 | private val viewModel by viewModels() 20 | 21 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 22 | super.onViewCreated(view, savedInstanceState) 23 | initViews() 24 | initListeners() 25 | collectUIState() 26 | collectUiEvents() 27 | } 28 | 29 | private fun collectUiEvents() = viewModel.events.launchAndCollect(viewLifecycleOwner) { 30 | when (it) { 31 | RegisterScreenEvents.NavigateToHomeScreen -> requireActivity().navigate( 32 | DocuBoxActivity::class.java, 33 | true 34 | ) 35 | RegisterScreenEvents.NavigateToLoginScreen -> findNavController().navigate(R.id.action_registerFragment_to_loginFragment) 36 | is RegisterScreenEvents.ShowToast -> requireContext().showToast(it.message) 37 | } 38 | } 39 | 40 | private fun collectUIState() = viewModel.uiState.launchAndCollectLatest(viewLifecycleOwner) { 41 | // change progress bar visibility acc to isLoading 42 | with(binding) { 43 | emailTIL.error = it.emailError 44 | passwordTIL.error = it.passwordError 45 | usernameTIL.error = it.usernameError 46 | emailTIET.isEnabled = it.areTextFieldsEnabled 47 | passwordTIET.isEnabled = it.areTextFieldsEnabled 48 | usernameTIET.isEnabled = it.areTextFieldsEnabled 49 | registerBtn.isEnabled = it.isRegisterButtonEnabled 50 | progressLayout.visibleOrGone(it.isLoading) 51 | } 52 | } 53 | 54 | private fun initListeners() = with(binding) { 55 | emailTIET.listenAfterChange(viewModel::onEmailTextChange) 56 | passwordTIET.listenAfterChange(viewModel::onPasswordTextChange) 57 | usernameTIET.listenAfterChange(viewModel::onUsernameChange) 58 | registerBtn.singleClick(viewModel::onRegisterButtonPressed) 59 | goToLogin.singleClick(viewModel::onGoToLoginPress) 60 | } 61 | 62 | private fun initViews() = with(binding) {} 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/register/RegisterScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.register 2 | 3 | sealed class RegisterScreenEvents { 4 | data class ShowToast(val message: String) : RegisterScreenEvents() 5 | object NavigateToLoginScreen : RegisterScreenEvents() 6 | object NavigateToHomeScreen : RegisterScreenEvents() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/register/RegisterScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.register 2 | 3 | data class RegisterScreenState( 4 | val email: String = "", 5 | val password: String = "", 6 | val username: String = "", 7 | val isLoading: Boolean = false, 8 | 9 | val usernameError: String? = null, 10 | val emailError: String? = null, 11 | val passwordError: String? = null 12 | ) { 13 | val isRegisterButtonEnabled: Boolean 14 | get() = email.isNotBlank() && password.isNotBlank() && username.isNotBlank() && !isLoading 15 | 16 | val areTextFieldsEnabled: Boolean 17 | get() = !isLoading 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/auth/register/RegisterViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.auth.register 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.docubox.data.repo.AuthRepoImpl 6 | import com.docubox.domain.repo.AuthRepo 7 | import com.docubox.util.Resource 8 | import com.docubox.util.validateEmail 9 | import com.docubox.util.validatePassword 10 | import com.docubox.util.validateUsername 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import kotlinx.coroutines.flow.* 13 | import kotlinx.coroutines.launch 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class RegisterViewModel @Inject constructor(private val repo: AuthRepo) : ViewModel() { 18 | 19 | private val _uiState = MutableStateFlow(RegisterScreenState()) 20 | val uiState = _uiState.asStateFlow() 21 | 22 | private val _events = MutableSharedFlow() 23 | val events = _events.asSharedFlow() 24 | 25 | fun onEmailTextChange(email: String) = viewModelScope.launch { 26 | _uiState.update { it.copy(email = email.trim()) } 27 | } 28 | 29 | fun onPasswordTextChange(password: String) = viewModelScope.launch { 30 | _uiState.update { it.copy(password = password.trim()) } 31 | } 32 | 33 | fun onUsernameChange(username: String) = viewModelScope.launch { 34 | _uiState.update { it.copy(username = username.trim()) } 35 | } 36 | 37 | private fun verifyUserInput(): Boolean { 38 | val emailError = uiState.value.email.validateEmail() 39 | val passwordError = uiState.value.password.validatePassword() 40 | val usernameError = uiState.value.username.validateUsername() 41 | _uiState.update { 42 | it.copy( 43 | emailError = emailError, 44 | passwordError = passwordError, 45 | usernameError = usernameError 46 | ) 47 | } 48 | return listOf(emailError, passwordError, usernameError).all { it == null } 49 | } 50 | 51 | fun onRegisterButtonPressed() = viewModelScope.launch { 52 | if (verifyUserInput()) 53 | registerUsingCredentials( 54 | uiState.value.username, 55 | uiState.value.email, 56 | uiState.value.password 57 | ) 58 | } 59 | 60 | fun onGoToLoginPress() = viewModelScope.launch { 61 | _events.emit(RegisterScreenEvents.NavigateToLoginScreen) 62 | } 63 | 64 | private suspend fun registerUsingCredentials( 65 | username: String, 66 | email: String, 67 | password: String, 68 | ) { 69 | 70 | repo.registerUser(username, email, password).collectLatest { 71 | _uiState.emit(uiState.value.copy(isLoading = it is Resource.Loading)) 72 | when (it) { 73 | is Resource.Error -> _events.emit(RegisterScreenEvents.ShowToast(it.message)) 74 | is Resource.Loading -> Unit 75 | is Resource.Success -> _events.emit(RegisterScreenEvents.NavigateToHomeScreen) 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/dialogs/AboutUsBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.dialogs 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.docubox.BuildConfig 8 | import com.docubox.databinding.FragmentAboutUsBottomSheetBinding 9 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 10 | 11 | class AboutUsBottomSheetFragment : BottomSheetDialogFragment() { 12 | 13 | private lateinit var binding: FragmentAboutUsBottomSheetBinding 14 | 15 | override fun onCreateView( 16 | inflater: LayoutInflater, 17 | container: ViewGroup?, 18 | savedInstanceState: Bundle? 19 | ): View { 20 | binding = FragmentAboutUsBottomSheetBinding.inflate(inflater, container, false) 21 | return binding.root 22 | } 23 | 24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 25 | super.onViewCreated(view, savedInstanceState) 26 | val versionName = BuildConfig.VERSION_NAME 27 | binding.versionCodeTv.text = "Version: $versionName" 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/dialogs/FileOptionsBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.dialogs 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.docubox.data.local.models.FileOption 8 | import com.docubox.databinding.ItemOptionsBinding 9 | import com.docubox.databinding.OptionsBottomSheetBinding 10 | import com.docubox.util.extensions.compose 11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 12 | import dagger.hilt.android.AndroidEntryPoint 13 | 14 | @AndroidEntryPoint 15 | class FileOptionsBottomSheetFragment( 16 | private val items: List = emptyList(), 17 | private val onOptionSelected: (FileOption) -> Unit = {} 18 | ) : BottomSheetDialogFragment() { 19 | 20 | private lateinit var binding: OptionsBottomSheetBinding 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, 24 | container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View { 27 | binding = OptionsBottomSheetBinding.inflate(inflater, container, false) 28 | return binding.root 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | binding.optionsRv.setHasFixedSize(false) 34 | binding.optionsRv.compose( 35 | ItemOptionsBinding::inflate, 36 | onBind = { item: FileOption, pos -> 37 | text.text = item.text 38 | }, 39 | ) { 40 | onOptionSelected(this) 41 | dismiss() 42 | }.apply { submitList(items) } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/dialogs/FolderOptionsBottomSheetFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.dialogs 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.docubox.data.local.models.FolderOptions 8 | import com.docubox.databinding.ItemOptionsBinding 9 | import com.docubox.databinding.OptionsBottomSheetBinding 10 | import com.docubox.util.extensions.compose 11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 12 | import dagger.hilt.android.AndroidEntryPoint 13 | 14 | @AndroidEntryPoint 15 | class FolderOptionsBottomSheetFragment( 16 | private val items: List = emptyList(), 17 | private val onOptionSelected: (FolderOptions) -> Unit = {} 18 | ) : BottomSheetDialogFragment() { 19 | 20 | private lateinit var binding: OptionsBottomSheetBinding 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, 24 | container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View { 27 | binding = OptionsBottomSheetBinding.inflate(inflater, container, false) 28 | return binding.root 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | binding.optionsRv.setHasFixedSize(false) 34 | binding.optionsRv.compose( 35 | ItemOptionsBinding::inflate, 36 | onBind = { item: FolderOptions, pos -> 37 | text.text = item.text 38 | }, 39 | ) { 40 | onOptionSelected(this) 41 | dismiss() 42 | }.apply { submitList(items) } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/DocuBoxActivity.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.navigation.NavController 6 | import androidx.navigation.findNavController 7 | import androidx.navigation.ui.setupWithNavController 8 | import com.docubox.R 9 | import com.docubox.databinding.ActivityDocuboxBinding 10 | import com.docubox.util.extensions.visibleOrGone 11 | import com.docubox.util.viewBinding.viewBinding 12 | import dagger.hilt.android.AndroidEntryPoint 13 | 14 | @AndroidEntryPoint 15 | class DocuBoxActivity : AppCompatActivity() { 16 | 17 | private val binding by viewBinding(ActivityDocuboxBinding::inflate) 18 | private lateinit var navController: NavController 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(binding.root) 23 | navController = findNavController(R.id.navHostFragment) 24 | setUpBottomNav() 25 | } 26 | 27 | private fun setUpBottomNav() = binding.apply { 28 | bottomNavView.setupWithNavController(navController) 29 | val hiddenInFragments = listOf(R.id.viewDocumentFragment, R.id.searchResultsFragment) 30 | navController.addOnDestinationChangedListener { _, destination, _ -> 31 | bottomNavView.visibleOrGone(destination.id !in hiddenInFragments) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/documents/DocumentsScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.documents 2 | 3 | sealed class DocumentsScreenEvents { 4 | data class ShowToast(val message: String) : DocumentsScreenEvents() 5 | object NavigateBack : DocumentsScreenEvents() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/documents/DocumentsScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.documents 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | 5 | data class DocumentsScreenState( 6 | val storageItems: List = emptyList(), 7 | val isLoading: Boolean = false, 8 | val isRefreshing: Boolean = false, 9 | val actionBarTitle: String = "Documents", 10 | val storageUsed: Float = 0f, 11 | val totalStorage: Float = 50f, 12 | ) 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.home 2 | 3 | import android.app.DownloadManager 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.view.View 7 | import android.view.inputmethod.EditorInfo 8 | import androidx.fragment.app.Fragment 9 | import androidx.fragment.app.viewModels 10 | import androidx.navigation.fragment.findNavController 11 | import com.docubox.R 12 | import com.docubox.data.local.models.FileType 13 | import com.docubox.data.local.models.SearchResult 14 | import com.docubox.data.local.models.StorageItem 15 | import com.docubox.databinding.FragmentHomeBinding 16 | import com.docubox.util.Constants.CONTACT_US_URL 17 | import com.docubox.util.Constants.HOW_TO_USE_URL 18 | import com.docubox.util.extensions.* 19 | import com.docubox.util.viewBinding.viewBinding 20 | import dagger.hilt.android.AndroidEntryPoint 21 | 22 | @AndroidEntryPoint 23 | class HomeFragment : Fragment(R.layout.fragment_home) { 24 | 25 | private val binding by viewBinding(FragmentHomeBinding::bind) 26 | private val viewModel by viewModels() 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | initActionBar() 31 | initViews() 32 | initListeners() 33 | collectUiState() 34 | collectUiEvents() 35 | } 36 | 37 | private fun collectUiState() = viewModel.uiState.launchAndCollectLatest(viewLifecycleOwner) { 38 | binding.apply { 39 | tvStorageConsumption.text = "${it.storageUsed} / ${it.totalStorage} MB" 40 | storageProgress.max = it.totalStorage.toInt() 41 | storageProgress.progress = it.storageUsed.toInt() % it.totalStorage.toInt() 42 | progressLayout.visibleOrGone(it.isLoading) 43 | } 44 | } 45 | 46 | private fun collectUiEvents() = viewModel.events.launchAndCollect(viewLifecycleOwner) { 47 | when (it) { 48 | is HomeScreenEvents.NavigateToSearchResults -> navigateToSearchResults( 49 | it.title, 50 | it.files 51 | ) 52 | is HomeScreenEvents.ShowToast -> requireContext().showToast(it.message) 53 | } 54 | } 55 | 56 | private fun initListeners() = with(binding) { 57 | searchBar.setOnEditorActionListener { _, actionId, _ -> 58 | if (actionId == EditorInfo.IME_ACTION_SEARCH) { 59 | viewModel.onSearch(searchBar.text.toString()) 60 | searchBar.hideKeyboard() 61 | } 62 | true 63 | } 64 | imageOption.singleClick { viewModel.onFileTypePress(FileType.Image) } 65 | videoOption.singleClick { viewModel.onFileTypePress(FileType.Video) } 66 | audioOption.singleClick { viewModel.onFileTypePress(FileType.Audio) } 67 | docsOption.singleClick { viewModel.onFileTypePress(FileType.File) } 68 | downloads.singleClick(this@HomeFragment::openDownloadsFolder) 69 | howToUse.singleClick { requireContext().openBrowser(HOW_TO_USE_URL) } 70 | aboutUs.singleClick { 71 | findNavController().navigate(R.id.action_homeFragment_to_aboutUsBottomSheetFragment) 72 | } 73 | contactUs.singleClick { requireContext().openBrowser(CONTACT_US_URL) } 74 | btnRefreshStorageConsumption.singleClick(viewModel::getStorageConsumption) 75 | } 76 | 77 | private fun initViews() = Unit 78 | 79 | private fun initActionBar() = with(binding) { 80 | actionBar.setupActionBar("Home") 81 | } 82 | 83 | private fun navigateToSearchResults(title: String, items: List) { 84 | val action = HomeFragmentDirections.actionHomeFragmentToSearchResultsFragment( 85 | SearchResult(items), title 86 | ) 87 | findNavController().navigate(action) 88 | } 89 | 90 | private fun openDownloadsFolder() { 91 | Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).also { 92 | startActivity(it) 93 | } 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/home/HomeScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.home 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | 5 | sealed class HomeScreenEvents { 6 | data class ShowToast(val message: String) : HomeScreenEvents() 7 | data class NavigateToSearchResults( 8 | val title: String, 9 | val files: List 10 | ) : HomeScreenEvents() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/home/HomeScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.home 2 | 3 | data class HomeScreenState( 4 | val storageUsed: Float = 0f, 5 | val totalStorage: Float = 50f, 6 | val isLoading: Boolean = false 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.home 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.docubox.data.local.models.FileType 6 | import com.docubox.data.local.models.StorageItem 7 | import com.docubox.data.remote.models.responses.StorageConsumption 8 | import com.docubox.domain.repo.StorageRepo 9 | import com.docubox.util.Resource 10 | import dagger.hilt.android.lifecycle.HiltViewModel 11 | import kotlinx.coroutines.flow.* 12 | import kotlinx.coroutines.launch 13 | import javax.inject.Inject 14 | 15 | @HiltViewModel 16 | class HomeViewModel @Inject constructor(private val repo: StorageRepo) : ViewModel() { 17 | 18 | private val _uiState = MutableStateFlow(HomeScreenState()) 19 | val uiState = _uiState.asStateFlow() 20 | 21 | private val _events = MutableSharedFlow() 22 | val events = _events.asSharedFlow() 23 | 24 | init { 25 | getStorageConsumption() 26 | } 27 | 28 | fun getStorageConsumption() = viewModelScope.launch { 29 | repo.getStorageConsumption().collectLatest { 30 | _uiState.emit(uiState.value.copy(isLoading = it is Resource.Loading)) 31 | when (it) { 32 | is Resource.Error -> _events.emit(HomeScreenEvents.ShowToast(it.message)) 33 | is Resource.Loading -> Unit 34 | is Resource.Success -> it.data?.let(this@HomeViewModel::handleStorageConsumptionSuccess) 35 | } 36 | } 37 | } 38 | 39 | private fun handleStorageConsumptionSuccess(storageConsumption: StorageConsumption) { 40 | _uiState.update { state -> 41 | state.copy( 42 | storageUsed = storageConsumption.storageConsumption.toFloatOrNull() ?: 0f, 43 | totalStorage = storageConsumption.totalStorage.toFloatOrNull() ?: 0f 44 | ) 45 | } 46 | } 47 | 48 | fun onSearch(query: String) = viewModelScope.launch { 49 | if (query.trim().isNotEmpty()) 50 | searchFiles(query.trim(), query.trim()) 51 | } 52 | 53 | fun onFileTypePress(fileType: FileType) = viewModelScope.launch { 54 | searchFiles(fileType.mimeType, fileType.title, true) 55 | } 56 | 57 | private suspend fun searchFiles(query: String, title: String, isByType: Boolean = false) { 58 | val res = if (!isByType) repo.searchFileByQuery(query) 59 | else repo.searchFileByType(query) 60 | res.collectLatest { 61 | _uiState.emit(uiState.value.copy(isLoading = it is Resource.Loading)) 62 | when (it) { 63 | is Resource.Error -> _events.emit(HomeScreenEvents.ShowToast(it.message)) 64 | is Resource.Loading -> Unit 65 | is Resource.Success -> it.data?.let { items -> 66 | handleSearchFileSuccess(title, items) 67 | } 68 | } 69 | } 70 | } 71 | 72 | private suspend fun handleSearchFileSuccess(title: String, files: List) { 73 | if (files.isEmpty()) _events.emit(HomeScreenEvents.ShowToast("No results found")) 74 | else _events.emit(HomeScreenEvents.NavigateToSearchResults(title, files)) 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/others/ViewDocumentFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.others 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.navigation.fragment.findNavController 7 | import androidx.navigation.fragment.navArgs 8 | import com.docubox.R 9 | import com.docubox.data.remote.models.responses.file.FileDto 10 | import com.docubox.databinding.FragmentViewDocumentBinding 11 | import com.docubox.util.Secrets.BASE_URL 12 | import com.docubox.util.extensions.setupActionBar 13 | import com.docubox.util.viewBinding.viewBinding 14 | 15 | 16 | class ViewDocumentFragment : Fragment(R.layout.fragment_view_document) { 17 | 18 | private val args: ViewDocumentFragmentArgs by navArgs() 19 | private val binding by viewBinding(FragmentViewDocumentBinding::bind) 20 | private lateinit var file: FileDto 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | file = args.file.file 25 | initActionBar() 26 | initWebView() 27 | } 28 | 29 | private fun initActionBar() = with(binding) { 30 | actionBar.setupActionBar( 31 | title = "Document Viewer", 32 | backButtonEnabled = true, 33 | backButtonOnClickListener = { findNavController().popBackStack() } 34 | ) 35 | } 36 | 37 | private fun initWebView() = with(binding) { 38 | webView.apply { 39 | settings.builtInZoomControls = true 40 | settings.supportZoom() 41 | loadUrl("${BASE_URL}documents/file/${file.id}") 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/profile/ProfileFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.profile 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.view.View 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.lifecycle.lifecycleScope 10 | import androidx.navigation.fragment.findNavController 11 | import com.docubox.R 12 | import com.docubox.data.local.models.User 13 | import com.docubox.databinding.FragmentProfileBinding 14 | import com.docubox.ui.screens.auth.AuthActivity 15 | import com.docubox.util.Constants.REPORT_BUG_URL 16 | import com.docubox.util.Constants.VIEW_SOURCE_CODE_URL 17 | import com.docubox.util.extensions.navigate 18 | import com.docubox.util.extensions.setupActionBar 19 | import com.docubox.util.extensions.showAlertDialog 20 | import com.docubox.util.extensions.singleClick 21 | import com.docubox.util.viewBinding.viewBinding 22 | import dagger.hilt.android.AndroidEntryPoint 23 | 24 | @AndroidEntryPoint 25 | class ProfileFragment : Fragment(R.layout.fragment_profile) { 26 | 27 | private val binding by viewBinding(FragmentProfileBinding::bind) 28 | private val viewModel by viewModels() 29 | private var user: User? = null 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | initActionBar() 34 | initViews() 35 | initClickListeners() 36 | } 37 | 38 | 39 | private fun initActionBar() = with(binding) { 40 | actionBar.setupActionBar("Profile", true, { 41 | findNavController().popBackStack() 42 | }) 43 | } 44 | 45 | 46 | private fun initViews() = with(binding) { 47 | user = viewModel.getUser() 48 | user?.let { 49 | tvUserName.text = it.userName 50 | tvUserEmail.text = it.userEmail 51 | } 52 | } 53 | 54 | private fun initClickListeners() = with(binding) { 55 | btnLogout.singleClick(this@ProfileFragment::handleLogout) 56 | btnAbout.singleClick { 57 | findNavController().navigate(R.id.action_profileFragment_to_aboutUsBottomSheetFragment) 58 | } 59 | btnReportBug.singleClick { openIntent(REPORT_BUG_URL) } 60 | btnViewSourceCode.singleClick { openIntent(VIEW_SOURCE_CODE_URL) } 61 | 62 | } 63 | 64 | private fun handleLogout() = viewLifecycleOwner.lifecycleScope.launchWhenStarted { 65 | requireContext().showAlertDialog( 66 | "Logout", 67 | "Are you sure you want to logout?", 68 | "Logout", 69 | "Cancel" 70 | ).also { 71 | if (it) { 72 | viewModel.logoutUser() 73 | requireActivity().navigate(AuthActivity::class.java, true) 74 | } 75 | } 76 | } 77 | 78 | private fun openIntent(url: String) { 79 | Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply { 80 | startActivity(this) 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/profile/ProfileViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.profile 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.docubox.domain.repo.AuthRepo 6 | import com.docubox.domain.repo.PreferenceRepo 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import kotlinx.coroutines.launch 9 | import javax.inject.Inject 10 | 11 | @HiltViewModel 12 | class ProfileViewModel @Inject constructor( 13 | private val preferenceRepo: PreferenceRepo, 14 | private val authRepo: AuthRepo, // Repository that contains data store functions 15 | ) : ViewModel() { 16 | // Function to get a user from datastore using Preference Repository 17 | fun getUser() = preferenceRepo.getUser() 18 | 19 | fun logoutUser() = viewModelScope.launch { 20 | authRepo.logoutUser() 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/searchResults/SearchResultsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.searchResults 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.fragment.app.Fragment 6 | import androidx.fragment.app.viewModels 7 | import androidx.navigation.fragment.findNavController 8 | import androidx.navigation.fragment.navArgs 9 | import com.docubox.R 10 | import com.docubox.data.local.models.FileOption 11 | import com.docubox.data.local.models.StorageItem 12 | import com.docubox.databinding.FragmentSearchResultsBinding 13 | import com.docubox.databinding.ItemStorageBinding 14 | import com.docubox.ui.adapter.OneAdapter 15 | import com.docubox.util.Constants 16 | import com.docubox.util.extensions.* 17 | import com.docubox.util.viewBinding.viewBinding 18 | import dagger.hilt.android.AndroidEntryPoint 19 | 20 | @AndroidEntryPoint 21 | class SearchResultsFragment : Fragment(R.layout.fragment_search_results) { 22 | 23 | private val binding by viewBinding(FragmentSearchResultsBinding::bind) 24 | private val viewModel by viewModels() 25 | private val args by navArgs() 26 | private lateinit var resultsAdapter: OneAdapter 27 | 28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 29 | super.onViewCreated(view, savedInstanceState) 30 | viewModel.setSearchResults(args.results.results) 31 | initViews() 32 | collectUiEvents() 33 | collectUiState() 34 | } 35 | 36 | private fun collectUiState() = viewModel.uiState.launchAndCollectLatest(viewLifecycleOwner) { 37 | resultsAdapter.submitList(it.items) 38 | binding.progressLayout.visibleOrGone(it.isLoading) 39 | } 40 | 41 | private fun collectUiEvents() = viewModel.events.launchAndCollect(viewLifecycleOwner) { 42 | when (it) { 43 | SearchResultsScreenEvents.NavigateBack -> findNavController().popBackStack() 44 | is SearchResultsScreenEvents.ShowToast -> requireContext().showToast(it.message) 45 | } 46 | } 47 | 48 | 49 | private fun initViews() = with(binding) { 50 | storageRv.setHasFixedSize(false) 51 | resultsAdapter = storageRv.compose( 52 | ItemStorageBinding::inflate, 53 | onBind = { item: StorageItem.File, _ -> 54 | title.text = item.name 55 | description.text = item.description 56 | itemImage.setImageResource(item.icon) 57 | root.setOnLongClickListener { 58 | onFileLongPressed(item) 59 | true 60 | } 61 | } 62 | ) { 63 | handleStorageItemPress(this) 64 | } 65 | actionBar.setupActionBar(args.title, true, { 66 | findNavController().popBackStack() 67 | }) 68 | } 69 | 70 | private fun handleStorageItemPress(item: StorageItem) { 71 | when (item) { 72 | is StorageItem.Folder -> Unit 73 | is StorageItem.File -> { 74 | val action = 75 | SearchResultsFragmentDirections.actionSearchResultsFragmentToViewDocumentFragment( 76 | item 77 | ) 78 | findNavController().navigate(action) 79 | } 80 | } 81 | } 82 | 83 | private fun onFileLongPressed(file: StorageItem.File) { 84 | val options = Constants.fileOptions.toMutableList().apply { 85 | if (file.file.fileSharedTo.isEmpty()) remove(FileOption.RevokeShare) 86 | } 87 | showFileOptions( 88 | file = file, 89 | options = options, 90 | onDelete = viewModel::deleteFile, 91 | onShare = viewModel::shareFile, 92 | onRevokeShare = viewModel::revokeShareFile, 93 | onDownload = viewModel::downloadFile, 94 | onRename = viewModel::renameFile 95 | ) 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/searchResults/SearchResultsScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.searchResults 2 | 3 | sealed class SearchResultsScreenEvents { 4 | data class ShowToast(val message: String) : SearchResultsScreenEvents() 5 | object NavigateBack : SearchResultsScreenEvents() 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/searchResults/SearchResultsScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.searchResults 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | 5 | data class SearchResultsScreenState( 6 | val items: List = emptyList(), 7 | val isLoading: Boolean = false 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/shared/SharedScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.shared 2 | 3 | sealed class SharedScreenEvents { 4 | data class ShowToast(val message: String) : SharedScreenEvents() 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/main/shared/SharedScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.main.shared 2 | 3 | import com.docubox.data.local.models.StorageItem 4 | 5 | data class SharedScreenState( 6 | val storageItems: List = emptyList(), 7 | val isLoading: Boolean = false, 8 | val isRefreshing: Boolean = false, 9 | val isSharedByMeState: Boolean = false 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/splash/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.splash 2 | 3 | import android.os.Bundle 4 | import androidx.activity.viewModels 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.docubox.R 7 | import com.docubox.ui.screens.auth.AuthActivity 8 | import com.docubox.ui.screens.main.DocuBoxActivity 9 | import com.docubox.util.extensions.launchAndCollect 10 | import com.docubox.util.extensions.navigate 11 | import dagger.hilt.android.AndroidEntryPoint 12 | 13 | @AndroidEntryPoint 14 | class SplashActivity : AppCompatActivity() { 15 | 16 | private val viewModel by viewModels() 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_splash) 21 | collectEvents() 22 | } 23 | 24 | private fun collectEvents() = viewModel.events.launchAndCollect(this) { 25 | when (it) { 26 | SplashScreenEvents.NavigateToAuthScreen -> navigate(AuthActivity::class.java, true) 27 | SplashScreenEvents.NavigateToHomeScreen -> navigate(DocuBoxActivity::class.java, true) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/splash/SplashScreenEvents.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.splash 2 | 3 | sealed class SplashScreenEvents { 4 | object NavigateToAuthScreen : SplashScreenEvents() 5 | object NavigateToHomeScreen : SplashScreenEvents() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/ui/screens/splash/SplashViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.ui.screens.splash 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.docubox.domain.repo.AuthRepo 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import kotlinx.coroutines.delay 8 | import kotlinx.coroutines.flow.MutableSharedFlow 9 | import kotlinx.coroutines.flow.asSharedFlow 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltViewModel 14 | class SplashViewModel @Inject constructor(private val authRepo: AuthRepo) : ViewModel() { 15 | 16 | private val _events = MutableSharedFlow() 17 | val events = _events.asSharedFlow() 18 | 19 | init { 20 | navigate() 21 | } 22 | 23 | private fun navigate() = viewModelScope.launch { 24 | delay(2000L) 25 | _events.emit(getNavigationEvent()) 26 | } 27 | 28 | private fun getNavigationEvent() = when { 29 | authRepo.isUserLoggedIn() -> SplashScreenEvents.NavigateToHomeScreen 30 | else -> SplashScreenEvents.NavigateToAuthScreen 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | import android.Manifest 4 | import android.os.Build 5 | import com.docubox.data.local.models.FileOption 6 | import com.docubox.data.local.models.FolderOptions 7 | 8 | // All our app's constant data variables 9 | object Constants { 10 | 11 | const val DATASTORE_NAME = "DocuBoxDataStore" 12 | 13 | const val FILE_OPTION_DIALOG = "FileOptionsDialog" 14 | const val FOLDER_OPTION_DIALOG = "FolderOptionsDialog" 15 | 16 | const val REPORT_BUG_URL = "https://github.com/ishantchauhan710/DocuBox-AndroidApp/issues" 17 | const val VIEW_SOURCE_CODE_URL = "https://github.com/ishantchauhan710/DocuBox-AndroidApp" 18 | 19 | const val HOW_TO_USE_URL = "https://github.com/Vaibhav2002/DocuBox-AndroidApp/blob/master/HOW%20TO%20USE.md" 20 | const val CONTACT_US_URL = "https://github.com/Vaibhav2002/DocuBox-AndroidApp/discussions" 21 | 22 | val folderOptions = listOf( 23 | FolderOptions.Rename, 24 | FolderOptions.Delete 25 | ) 26 | 27 | val fileOptions = listOf( 28 | FileOption.Rename, 29 | FileOption.Download, 30 | FileOption.Share, 31 | FileOption.RevokeShare, 32 | FileOption.Delete, 33 | ) 34 | 35 | val filePermissions = listOf( 36 | Manifest.permission.READ_EXTERNAL_STORAGE, 37 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 38 | ).apply { 39 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) 40 | toMutableList().add(Manifest.permission.MANAGE_EXTERNAL_STORAGE) 41 | } 42 | 43 | // val sampleStorageItems = listOf( 44 | // StorageItem.File( 45 | // id = "1", 46 | // name = "Ishant.pdf", 47 | // description = "120KB", 48 | // fileType = FileType.Document 49 | // ), 50 | // StorageItem.File( 51 | // id = "2", 52 | // name = "Vaibhav.exe", 53 | // description = "1.2MB", 54 | // fileType = FileType.File 55 | // ), 56 | // StorageItem.File( 57 | // id = "3", 58 | // name = "GoogleIO.mp4", 59 | // description = "120MB", 60 | // fileType = FileType.Video 61 | // ), 62 | // StorageItem.Folder(id = "4", name = "College", description = "10 files"), 63 | // StorageItem.Folder(id = "5", name = "Downloads", description = "2000 files"), 64 | // ).shuffled() 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/CredentialsValidator.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | import android.text.TextUtils 4 | import android.util.Patterns 5 | 6 | // Functions to validate credentials 7 | 8 | private const val PASSWORD_LENGTH = 6 9 | 10 | fun String.validateEmail(): String? = when { 11 | isEmpty() -> "Email cannot be empty" 12 | !isValidEmail() -> "Invalid email" 13 | else -> null 14 | } 15 | 16 | fun String.validatePassword(): String? = when { 17 | isEmpty() -> "Password cannot be empty" 18 | !isPasswordValid() -> "Minimum password length is $PASSWORD_LENGTH" 19 | else -> null 20 | } 21 | 22 | fun String.validateUsername() = when { 23 | isEmpty() -> "Username cannot be empty" 24 | else -> null 25 | } 26 | 27 | fun String.validateConfirmPassword(password: String) = when { 28 | password != this -> "Passwords do not match" 29 | else -> null 30 | } 31 | 32 | fun String.isValidEmail() = 33 | !TextUtils.isEmpty(this) && Patterns.EMAIL_ADDRESS.matcher(this).matches() 34 | 35 | fun String.isPasswordValid() = isNotEmpty() && length >= PASSWORD_LENGTH 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import android.provider.OpenableColumns 6 | import android.webkit.MimeTypeMap 7 | 8 | object FileUtil { 9 | fun getFileSize(context: Context, fileUri: Uri): Long { 10 | val returnCursor = context.contentResolver.query(fileUri, null, null, null, null) 11 | return if (returnCursor != null) { 12 | val sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE) 13 | returnCursor.moveToFirst() 14 | val fileSize: Long = returnCursor.getLong(sizeIndex) 15 | returnCursor.close() 16 | fileSize 17 | } else { 18 | -1 19 | } 20 | } 21 | 22 | fun getMimeTypeFromFile(fileName: String): String? { 23 | val mimeMap = MimeTypeMap.getSingleton() 24 | val extension = MimeTypeMap.getFileExtensionFromUrl(fileName) 25 | return mimeMap.getMimeTypeFromExtension(extension) 26 | } 27 | 28 | fun getParsedFileUrl(url: String): String { 29 | return if (url.startsWith("ap-")) { 30 | "https://$url" 31 | } else { 32 | url 33 | } 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/NotificationHelper.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | import android.app.Notification 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.content.Context 7 | import dagger.hilt.android.qualifiers.ApplicationContext 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | @Singleton 12 | class NotificationHelper @Inject constructor(@ApplicationContext private val context: Context) { 13 | 14 | companion object { 15 | const val CHANNEL_ID = "DocuBox_Channel" 16 | const val CHANNEL_NAME = "DocuBox" 17 | } 18 | 19 | private val notificationManager = 20 | context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 21 | 22 | fun setNotificationChannel() { 23 | val channel = NotificationChannel( 24 | CHANNEL_ID, 25 | CHANNEL_NAME, 26 | NotificationManager.IMPORTANCE_HIGH 27 | ).apply { 28 | lockscreenVisibility = Notification.VISIBILITY_PUBLIC 29 | } 30 | notificationManager.createNotificationChannel(channel) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | // Resource class for API calling 4 | sealed class Resource( 5 | open val data: T? = null, 6 | open val message: String = "", 7 | open val errorType: ErrorType = ErrorType.Unknown 8 | ) { 9 | 10 | class Loading : Resource() 11 | 12 | data class Success(override val data: T?, override val message: String = "") : 13 | Resource(data, message) 14 | 15 | data class Error( 16 | override val errorType: ErrorType = ErrorType.Unknown, 17 | override val message: String = errorType.errorMessage 18 | ) : Resource(null, message, errorType) 19 | } 20 | 21 | sealed class ErrorType(open val title: String, open val errorMessage: String) { 22 | object NoInternet : ErrorType( 23 | "No Internet", 24 | "Looks like you don't have an active internet connection" 25 | ) 26 | 27 | object Unknown : ErrorType("Unknown Error", "Oops something went wrong") 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/SafeApiCall.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | import com.docubox.data.remote.models.MessageResponse 4 | import com.google.gson.Gson 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import okhttp3.ResponseBody 8 | import retrofit2.Response 9 | import timber.log.Timber 10 | import java.io.IOException 11 | 12 | // Function to safely make API calls and handle errors using timber and resource class 13 | suspend fun safeApiCall( 14 | successMessage: String = "", 15 | errorMessage: String? = null, 16 | call: suspend () -> Response 17 | ): Resource = withContext(Dispatchers.IO) { 18 | return@withContext try { 19 | val response = call() 20 | if (response.isSuccessful) { 21 | response.body()?.let { Resource.Success(it, successMessage) } 22 | ?: Resource.Error(message = errorMessage ?: "DATA NULL") 23 | } else Resource.Error( 24 | // git error fixing 25 | message = errorMessage ?: getMessageFromErrorResponse(response.errorBody()) 26 | ) 27 | } catch (e: IOException) { 28 | Timber.d(e.toString()) 29 | Resource.Error(ErrorType.NoInternet, message = errorMessage ?: e.message.toString()) 30 | } catch (e: Exception) { 31 | Timber.d(e.toString()) 32 | Resource.Error(ErrorType.Unknown, message = errorMessage ?: e.message.toString()) 33 | } 34 | } 35 | 36 | fun getMessageFromErrorResponse(error: ResponseBody?): String { 37 | return error?.let { Gson().fromJson(it.charStream(), MessageResponse::class.java).message } 38 | ?: "Unknown Error Occurred" 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/SafeCall.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import timber.log.Timber 7 | 8 | // Function to safely call a function and handle errors using timber logging and resource class 9 | fun runSafe(call: () -> T): Resource = try { 10 | val result = call() 11 | Resource.Success(result) 12 | } catch (e: Exception) { 13 | Timber.d(e.message.toString()) 14 | Resource.Error(message = e.message.toString()) 15 | } 16 | 17 | // Function to call runSafe function asynchronously 18 | suspend fun runSafeAsync( 19 | dispatcher: CoroutineDispatcher = Dispatchers.IO, 20 | call: () -> T 21 | ): Resource = withContext(dispatcher) { 22 | return@withContext runSafe(call) 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/Secrets.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util 2 | 3 | object Secrets { 4 | 5 | const val BASE_URL = "" 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/extensions/ContextExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.extensions 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.view.LayoutInflater 7 | import android.widget.Toast 8 | import com.docubox.databinding.TextInputDialogBinding 9 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 10 | import kotlin.coroutines.resume 11 | import kotlin.coroutines.suspendCoroutine 12 | 13 | // Function to show an alert dialog 14 | suspend fun Context.showAlertDialog( 15 | title: String, 16 | message: String = "", 17 | positiveButtonText: String, 18 | negativeButtonText: String = "Cancel", 19 | ) = suspendCoroutine { 20 | val dialog = MaterialAlertDialogBuilder(this).apply { 21 | setTitle(title) 22 | if (message.isNotEmpty()) 23 | setMessage(message) 24 | setPositiveButton(positiveButtonText) { _, _ -> 25 | it.resume(true) 26 | } 27 | setNegativeButton(negativeButtonText) { _, _ -> 28 | it.resume(false) 29 | } 30 | } 31 | dialog.show() 32 | } 33 | 34 | // Function to show a toast message 35 | fun Context.showToast(message: String) { 36 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 37 | } 38 | 39 | 40 | suspend fun Context.showInputDialog( 41 | title: String, 42 | text: String = "", 43 | placeholder: String, 44 | label: String, 45 | ) = suspendCoroutine { 46 | val binding = TextInputDialogBinding.inflate(LayoutInflater.from(this)).apply { 47 | nameTIET.setText(text) 48 | nameTIL.placeholderText = placeholder 49 | nameTIL.hint = label 50 | } 51 | 52 | MaterialAlertDialogBuilder(this).apply { 53 | setTitle(title) 54 | setView(binding.root) 55 | setPositiveButton("Confirm") { _, _ -> 56 | it.resume(binding.nameTIET.text.toString()) 57 | } 58 | setNegativeButton("Cancel") { _, _ -> 59 | it.resume(text) 60 | } 61 | show() 62 | } 63 | } 64 | 65 | suspend fun Context.showSelectItemDialog( 66 | title: String, 67 | items: List, 68 | ) = suspendCoroutine { 69 | var selectedItemIndex = 0 70 | MaterialAlertDialogBuilder(this).apply { 71 | setTitle(title) 72 | setSingleChoiceItems(items.toTypedArray(), 0) { _, pos -> 73 | selectedItemIndex = pos 74 | } 75 | setNegativeButton("Cancel") { _, _ -> it.resume(null) } 76 | setPositiveButton("Confirm") { _, _ -> 77 | it.resume(items[selectedItemIndex]) 78 | } 79 | show() 80 | } 81 | } 82 | 83 | fun Context.openBrowser(url: String) { 84 | Intent(Intent.ACTION_VIEW, Uri.parse(url)).also { 85 | startActivity(it) 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/extensions/FlowExt.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.extensions 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.lifecycleScope 6 | import androidx.lifecycle.repeatOnLifecycle 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.collect 10 | import kotlinx.coroutines.flow.collectLatest 11 | import kotlinx.coroutines.launch 12 | 13 | // Function to launch a flow and collect it's latest value 14 | inline fun Flow.launchAndCollectLatest( 15 | owner: LifecycleOwner, 16 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED, 17 | crossinline action: suspend CoroutineScope.(T) -> Unit 18 | ) = owner.lifecycleScope.launch { 19 | owner.repeatOnLifecycle(minActiveState) { 20 | this@launchAndCollectLatest.collectLatest { 21 | action(it) 22 | } 23 | } 24 | } 25 | 26 | // Function to launch a flow 27 | inline fun Flow.launchAndCollect( 28 | owner: LifecycleOwner, 29 | minActiveState: Lifecycle.State = Lifecycle.State.STARTED, 30 | crossinline action: suspend CoroutineScope.(T) -> Unit 31 | ) = owner.lifecycleScope.launch { 32 | owner.repeatOnLifecycle(minActiveState) { 33 | this@launchAndCollect.collect { 34 | action(it) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/extensions/FolderOptionsExt.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.extensions 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.lifecycle.lifecycleScope 5 | import com.docubox.data.local.models.FolderOptions 6 | import com.docubox.data.local.models.StorageItem 7 | import com.docubox.ui.screens.dialogs.FolderOptionsBottomSheetFragment 8 | import com.docubox.util.Constants 9 | 10 | fun Fragment.showFolderOptions( 11 | folder: StorageItem.Folder, 12 | options: List, 13 | onDelete: (StorageItem.Folder) -> Unit = {}, 14 | onRename: (StorageItem.Folder, String) -> Unit = { _, _ -> } 15 | ) { 16 | FolderOptionsBottomSheetFragment(options) { 17 | when (it) { 18 | FolderOptions.Delete -> handleDeleteFolder(folder, onDelete) 19 | FolderOptions.Rename -> handleRenameFolder(folder, onRename) 20 | FolderOptions.RevokeShare -> Unit 21 | FolderOptions.Share -> Unit 22 | } 23 | }.show(childFragmentManager, Constants.FOLDER_OPTION_DIALOG) 24 | } 25 | 26 | private fun Fragment.handleDeleteFolder( 27 | folder: StorageItem.Folder, 28 | onDelete: (StorageItem.Folder) -> Unit 29 | ) = viewLifecycleOwner.lifecycleScope.launchWhenStarted { 30 | requireContext().showAlertDialog( 31 | title = "Delete Folder", 32 | message = "Are you sure you want to delete this folder?", 33 | positiveButtonText = "Delete" 34 | ).also { 35 | if (it) onDelete(folder) 36 | } 37 | } 38 | 39 | private fun Fragment.handleRenameFolder( 40 | folder: StorageItem.Folder, 41 | onRename: (StorageItem.Folder, String) -> Unit 42 | ) = viewLifecycleOwner.lifecycleScope.launchWhenStarted { 43 | requireContext().showInputDialog( 44 | title = "Rename Folder", 45 | text = folder.name, 46 | placeholder = "Enter folder name", 47 | label = "Folder Name" 48 | ).also { 49 | if (it.isNotEmpty() && it != folder.name) onRename(folder, it) 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/extensions/ResourceExt.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.extensions 2 | 3 | import com.docubox.util.Resource 4 | 5 | // Extension functions for resource class 6 | 7 | inline infix fun Resource.mapTo(change: (T) -> F): Resource = when (this) { 8 | is Resource.Error -> Resource.Error(errorType, message) 9 | is Resource.Loading -> Resource.Loading() 10 | is Resource.Success -> Resource.Success(data?.let { change(it) }, message) 11 | } 12 | 13 | fun Resource<*>.mapToUnit() = this mapTo {} 14 | 15 | fun Resource.mapMessages( 16 | successMessage: String? = null, 17 | errorMessage: String? = null 18 | ): Resource = when (this) { 19 | is Resource.Error -> copy(message = errorMessage ?: message) 20 | is Resource.Success -> copy(message = successMessage ?: message) 21 | else -> this 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/extensions/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.extensions 2 | 3 | import android.app.Activity 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.view.inputmethod.InputMethodManager 8 | import android.widget.EditText 9 | import androidx.core.widget.doAfterTextChanged 10 | import androidx.recyclerview.widget.RecyclerView 11 | import androidx.viewbinding.ViewBinding 12 | import com.docubox.R 13 | import com.docubox.ui.adapter.OneAdapter 14 | import com.google.android.material.button.MaterialButton 15 | import kotlinx.coroutines.* 16 | 17 | 18 | // Function to launch an onclick listener 19 | fun View.singleClick(onClick: () -> Unit) { 20 | setOnClickListener { 21 | onClick() 22 | } 23 | } 24 | 25 | // Function to call a function after edit text value changes 26 | fun EditText.listenAfterChange(afterChange: (String) -> Unit) { 27 | doAfterTextChanged { afterChange(it.toString()) } 28 | } 29 | 30 | // Function to toggle visibility of a view 31 | fun View.visibleOrGone(isVisible: Boolean) { 32 | visibility = if (isVisible) View.VISIBLE else View.GONE 33 | } 34 | 35 | // Function to launch an onclick listener after a delay 36 | fun View.setOnDelayClickListener(delayTime: Long, onClick: () -> Unit) { 37 | setOnLongClickListener { 38 | CoroutineScope(Dispatchers.IO).launch { 39 | delay(delayTime) 40 | withContext(Dispatchers.Main) { 41 | if (it.isPressed) { 42 | onClick() 43 | } 44 | } 45 | } 46 | true 47 | } 48 | } 49 | 50 | // Function to compose the recyclerview using OneAdapter class which we created 51 | fun RecyclerView.compose( 52 | layout: (LayoutInflater, ViewGroup?, Boolean) -> VB, 53 | onBind: VB.(ITEM, Int) -> Unit, 54 | itemClick: ITEM.(View) -> Unit = {} 55 | ): OneAdapter { 56 | return OneAdapter(layout, onBind, itemClick).also { adapter = it } 57 | } 58 | 59 | fun MaterialButton.setSelectedState(isSelected: Boolean) { 60 | setBackgroundColor( 61 | resources.getColor( 62 | if (isSelected) R.color.colorPrimary else R.color.colorAppBackground, 63 | ) 64 | ) 65 | setTextColor( 66 | resources.getColor( 67 | if (isSelected) R.color.colorOnPrimary else R.color.colorPrimaryText, 68 | ) 69 | ) 70 | } 71 | 72 | fun View.hideKeyboard() { 73 | (context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager).also { 74 | it.hideSoftInputFromWindow(windowToken, 0) 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/viewBinding/FragmentViewBindingDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.viewBinding 2 | 3 | // https://github.com/Zhuinden/fragmentviewbindingdelegate-kt 4 | 5 | import android.view.View 6 | import androidx.fragment.app.Fragment 7 | import androidx.lifecycle.DefaultLifecycleObserver 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.LifecycleOwner 10 | import androidx.lifecycle.Observer 11 | import androidx.viewbinding.ViewBinding 12 | import kotlin.properties.ReadOnlyProperty 13 | import kotlin.reflect.KProperty 14 | 15 | class FragmentViewBindingDelegate( 16 | val fragment: Fragment, 17 | val viewBindingFactory: (View) -> T 18 | ) : ReadOnlyProperty { 19 | private var binding: T? = null 20 | 21 | init { 22 | fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { 23 | val viewLifecycleOwnerLiveDataObserver = 24 | Observer { 25 | val viewLifecycleOwner = it ?: return@Observer 26 | 27 | viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { 28 | override fun onDestroy(owner: LifecycleOwner) { 29 | binding = null 30 | } 31 | }) 32 | } 33 | 34 | override fun onCreate(owner: LifecycleOwner) { 35 | fragment.viewLifecycleOwnerLiveData.observeForever( 36 | viewLifecycleOwnerLiveDataObserver 37 | ) 38 | } 39 | 40 | override fun onDestroy(owner: LifecycleOwner) { 41 | fragment.viewLifecycleOwnerLiveData.removeObserver( 42 | viewLifecycleOwnerLiveDataObserver 43 | ) 44 | } 45 | }) 46 | } 47 | 48 | override fun getValue(thisRef: Fragment, property: KProperty<*>): T { 49 | val binding = binding 50 | if (binding != null) { 51 | return binding 52 | } 53 | 54 | val lifecycle = fragment.viewLifecycleOwner.lifecycle 55 | if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { 56 | throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") 57 | } 58 | 59 | return viewBindingFactory(thisRef.requireView()).also { this.binding = it } 60 | } 61 | } 62 | 63 | fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = 64 | FragmentViewBindingDelegate(this, viewBindingFactory) 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/docubox/util/viewBinding/ViewBindingDelegate.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.util.viewBinding 2 | 3 | import android.os.Looper 4 | import android.view.LayoutInflater 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.lifecycle.Lifecycle 7 | import androidx.lifecycle.LifecycleObserver 8 | import androidx.lifecycle.OnLifecycleEvent 9 | import androidx.viewbinding.ViewBinding 10 | import kotlin.properties.ReadOnlyProperty 11 | import kotlin.reflect.KProperty 12 | 13 | /** 14 | * Reified keyword is used to get details of a class type at runtime 15 | * Inline keyword directly pastes function code into compiled code instead of copying it again and hence saves memory 16 | * This function takes layout inflater as a parameter and enables us to use view binding in our app in a simplified way 17 | * We pass the parameters in a class object: ViewBindingPropertyDelegate 18 | */ 19 | inline fun AppCompatActivity.viewBinding(noinline initializer: (LayoutInflater) -> T) = 20 | ViewBindingPropertyDelegate(this, initializer) 21 | 22 | class ViewBindingPropertyDelegate( 23 | private val activity: AppCompatActivity, 24 | private val initializer: (LayoutInflater) -> T 25 | ) : ReadOnlyProperty, LifecycleObserver { 26 | 27 | private var _value: T? = null 28 | 29 | init { 30 | activity.lifecycle.addObserver(this) 31 | } 32 | 33 | @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) 34 | @Suppress("Unused") 35 | fun onCreate() { 36 | if (_value == null) { 37 | _value = initializer(activity.layoutInflater) 38 | } 39 | activity.setContentView(_value?.root!!) 40 | activity.lifecycle.removeObserver(this) 41 | } 42 | 43 | override fun getValue(thisRef: AppCompatActivity, property: KProperty<*>): T { 44 | if (_value == null) { 45 | 46 | // This must be on the main thread only 47 | if (Looper.myLooper() != Looper.getMainLooper()) { 48 | throw IllegalThreadStateException("This cannot be called from other threads. It should be on the main thread only.") 49 | } 50 | 51 | _value = initializer(thisRef.layoutInflater) 52 | } 53 | return _value!! 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/ic_audio.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_document.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/ic_document.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/ic_file.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/ic_folder.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/ic_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/ic_video.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/img_about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/img_about.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/img_avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/img_avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/img_contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/img_contact.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/img_documentation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/img_documentation.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/img_downloads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/img_downloads.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/img_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable-v24/img_lock.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/drawable/app_logo.jpeg -------------------------------------------------------------------------------- /app/src/main/res/drawable/et_home_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bug.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_change_avatar.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_code.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_create_folder.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_document_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_downloads.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file_upload.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_grid.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logout.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profile.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_server_status.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_shared_folder.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_upload_folder.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/font/popping_extra_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/popping_extra_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/poppins_black.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/poppins_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/poppins_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/poppins_medium.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/poppins_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/poppins_semi_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/font/poppins_semi_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/action_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 35 | 36 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_docubox.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/empty_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/empty_state2.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_about_us_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 19 | 20 | 24 | 25 | 26 | 27 | 34 | 35 | 42 | 43 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_documents.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 17 | 18 | 26 | 27 | 35 | 36 | 37 | 38 | 39 | 48 | 49 | 61 | 62 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_search_results.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_view_document.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_options.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 24 | 25 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_storage.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 23 | 24 | 33 | 34 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/options_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 18 | 19 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/progress_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/sheet_upload_document.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 25 | 26 | 34 | 35 | 36 | 37 | 46 | 47 | 53 | 54 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/text_input_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 24 | 25 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/bottom_nav_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_logo_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-hdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-hdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-hdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-mdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-mdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-mdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xhdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xhdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xxhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xxhdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xxhdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xxxhdpi/ic_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_logo_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xxxhdpi/ic_logo_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_logo_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/app/src/main/res/mipmap-xxxhdpi/ic_logo_round.png -------------------------------------------------------------------------------- /app/src/main/res/navigation/auth_nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 19 | 20 | 25 | 29 | 30 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/main_nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 19 | 20 | 21 | 26 | 29 | 30 | 31 | 36 | 39 | 40 | 41 | 46 | 49 | 50 | 55 | 58 | 61 | 64 | 65 | 70 | 73 | 74 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #f2f6fb 5 | 6 | #479dff 7 | #ffffff 8 | 9 | #3381fe 10 | #ffffff 11 | 12 | #f95e07 13 | #ffffff 14 | 15 | #1F2528 16 | #404749 17 | #8A8E90 18 | 19 | #909090 20 | #DEDEDE 21 | #94C6FF 22 | #59000000 23 | 24 | #EBEAF0 25 | 26 | #3381ff 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 75dp 6 | 7 | 10dp 8 | 5dp 9 | 10dp 10 | 35sp 11 | 12 | 20dp 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_logo_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DocuBox 3 | Hello blank fragment 4 | OK 5 | Cancel 6 | Core features are based on these permission 7 | You need to allow necessary permissions in Settings manually 8 | Hello Again! 9 | Welcome back you\'ve been missed 10 | Email 11 | Password 12 | Login 13 | Don\'t have an account yet? 14 | Register 15 | Welcome 16 | Securely store your files with DocuBox 17 | Full Name 18 | Already have an account? 19 | Securely store\nyour files 20 | Save all your important documents on the cloud with maximum security 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 34 | 35 | 38 | 39 | 47 | 48 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/com/docubox/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/test/java/com/docubox/data/local/fileEncryptor/AliceFileEncryptorTest.kt: -------------------------------------------------------------------------------- 1 | package com.docubox.data.local.fileEncryptor 2 | 3 | import com.docubox.util.EncryptionDetails 4 | import com.docubox.util.Resource 5 | import com.google.common.truth.Truth.assertThat 6 | import com.rockaport.alice.Alice 7 | import com.rockaport.alice.AliceContextBuilder 8 | import kotlinx.coroutines.test.runTest 9 | import org.junit.Before 10 | import org.junit.Test 11 | import kotlin.random.Random 12 | 13 | class AliceFileEncryptorTest { 14 | 15 | private lateinit var alice: Alice 16 | private lateinit var aliceFileEncryptor: AliceFileEncryptor 17 | 18 | private fun getAlice(): Alice { 19 | val aliceContext = AliceContextBuilder().setAlgorithm(EncryptionDetails.algo) 20 | .setMode(EncryptionDetails.mode) 21 | .setIvLength(EncryptionDetails.ivLength) 22 | .setGcmTagLength(EncryptionDetails.gcmTagLength) 23 | .build() 24 | return Alice(aliceContext) 25 | } 26 | 27 | @Before 28 | fun setUp() { 29 | alice = getAlice() 30 | aliceFileEncryptor = AliceFileEncryptor(alice) 31 | } 32 | 33 | @Test 34 | fun `file is encrypted correctly`() = runTest { 35 | val sampleBytes = ByteArray(20) 36 | Random.nextBytes(sampleBytes) 37 | val encryptedBytes = aliceFileEncryptor.encryptFile(sampleBytes) 38 | assertThat(encryptedBytes).isInstanceOf(Resource.Success::class.java) 39 | assertThat(sampleBytes).isNotEqualTo(encryptedBytes.data) 40 | } 41 | 42 | @Test 43 | fun `file is decrypted correctly`() = runTest { 44 | val sampleBytes = ByteArray(20) 45 | Random.nextBytes(sampleBytes) 46 | val encryptedBytes = aliceFileEncryptor.encryptFile(sampleBytes) 47 | 48 | // success encryption 49 | assertThat(encryptedBytes).isInstanceOf(Resource.Success::class.java) 50 | assertThat(sampleBytes).isNotEqualTo(encryptedBytes.data) 51 | 52 | val decryptedBytes = aliceFileEncryptor.decryptFile(encryptedBytes.data!!) 53 | 54 | // success decryption 55 | assertThat(decryptedBytes).isInstanceOf(Resource.Success::class.java) 56 | assertThat(sampleBytes).isEqualTo(decryptedBytes.data) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext { 4 | kotlin_version = '1.7.0-RC' 5 | hilt_version = '2.42' 6 | nav_version = "2.4.2" 7 | lifecycle_version = "2.4.1" 8 | } 9 | 10 | dependencies { 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" 13 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" 14 | } 15 | } 16 | 17 | plugins { 18 | id 'com.android.application' version '7.2.0' apply false 19 | id 'com.android.library' version '7.2.0' apply false 20 | id 'org.jetbrains.kotlin.android' version '1.6.21' apply false 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 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 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 03 11:45:29 IST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /media/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/architecture.png -------------------------------------------------------------------------------- /media/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/cover.png -------------------------------------------------------------------------------- /media/graphicA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/graphicA.png -------------------------------------------------------------------------------- /media/graphicB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/graphicB.png -------------------------------------------------------------------------------- /media/graphicC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/graphicC.png -------------------------------------------------------------------------------- /media/package structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/package structure.png -------------------------------------------------------------------------------- /media/screenshots/about_us.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/about_us.jpg -------------------------------------------------------------------------------- /media/screenshots/document.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/document.jpg -------------------------------------------------------------------------------- /media/screenshots/documents_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/documents_2.jpg -------------------------------------------------------------------------------- /media/screenshots/getting_started.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/getting_started.jpg -------------------------------------------------------------------------------- /media/screenshots/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/home.jpg -------------------------------------------------------------------------------- /media/screenshots/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/login.jpg -------------------------------------------------------------------------------- /media/screenshots/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/profile.jpg -------------------------------------------------------------------------------- /media/screenshots/register.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/register.jpg -------------------------------------------------------------------------------- /media/screenshots/shared_by_you.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/shared_by_you.jpg -------------------------------------------------------------------------------- /media/screenshots/shared_to_me.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/shared_to_me.jpg -------------------------------------------------------------------------------- /media/screenshots/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/splash.jpg -------------------------------------------------------------------------------- /media/screenshots/videos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/screenshots/videos.jpg -------------------------------------------------------------------------------- /media/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vaibhav2002/DocuBox-AndroidApp/5e8bccbc4c6dd1cb3c004224a4a407ebc913ec12/media/summary.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url 'https://jitpack.io' } 14 | } 15 | } 16 | rootProject.name = "DocuBox" 17 | include ':app' 18 | --------------------------------------------------------------------------------