├── .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 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/google-java-format.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/ktfmt.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/ktlint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 |
6 |
7 |
8 |
9 |
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------