├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── ids.xml
│ │ │ │ ├── ic_launcher_1_background.xml
│ │ │ │ ├── ic_launcher_2_background.xml
│ │ │ │ └── colors.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ ├── provider_paths.xml
│ │ │ │ ├── data_extraction_rules.xml
│ │ │ │ └── edit_tags_motion_scene.xml
│ │ │ ├── drawable
│ │ │ │ ├── tags_divider.xml
│ │ │ │ ├── bg_rounded_8.xml
│ │ │ │ ├── bg_rounded_16dp.xml
│ │ │ │ ├── bg_dialog_rounded_16dp.xml
│ │ │ │ ├── bg_rounded_6_top.xml
│ │ │ │ ├── bg_rounded_top_24dp.xml
│ │ │ │ ├── ic_arrow_up.xml
│ │ │ │ ├── ic_arrow_down.xml
│ │ │ │ ├── ic_play.xml
│ │ │ │ ├── ic_chevron_right_black_24dp.xml
│ │ │ │ ├── ic_star_24.xml
│ │ │ │ ├── ic_star.xml
│ │ │ │ ├── ic_drag_handle.xml
│ │ │ │ ├── ic_sort.xml
│ │ │ │ ├── ic_list_add.xml
│ │ │ │ ├── ic_baseline_add.xml
│ │ │ │ ├── ic_arrow_right.xml
│ │ │ │ ├── bg_rounded_black_transparent.xml
│ │ │ │ ├── bg_rounded_black_transparent_small.xml
│ │ │ │ ├── ic_delete.xml
│ │ │ │ ├── ic_folder_action_add.xml
│ │ │ │ ├── order_ascending.xml
│ │ │ │ ├── order_descending.xml
│ │ │ │ ├── ic_baseline_delete_24.xml
│ │ │ │ ├── ic_folder_tree_node_add.xml
│ │ │ │ ├── ripple_simple_bg.xml
│ │ │ │ ├── ic_info.xml
│ │ │ │ ├── ic_close.xml
│ │ │ │ ├── ic_baseline_folder.xml
│ │ │ │ ├── ic_folder_tree_node_navigate.xml
│ │ │ │ ├── ic_menu_explore.xml
│ │ │ │ ├── ic_baseline_info.xml
│ │ │ │ ├── ic_push_pin.xml
│ │ │ │ ├── ic_cloud_on.xml
│ │ │ │ ├── ic_baseline_edit_24.xml
│ │ │ │ ├── ic_vert_dots.xml
│ │ │ │ ├── ic_baseline_favorite_24.xml
│ │ │ │ ├── ic_edit_file.xml
│ │ │ │ ├── ic_baseline_chooser_24.xml
│ │ │ │ ├── ic_menu_navigate.xml
│ │ │ │ ├── ic_folder_action_navigate.xml
│ │ │ │ ├── ic_baseline_search_24.xml
│ │ │ │ ├── ic_cloud_off.xml
│ │ │ │ ├── ic_file.xml
│ │ │ │ ├── radio_button_bg.xml
│ │ │ │ ├── ic_file_flv.xml
│ │ │ │ ├── ic_file_zip.xml
│ │ │ │ ├── ic_file_txt.xml
│ │ │ │ ├── ic_file_gif.xml
│ │ │ │ ├── ic_file_avi.xml
│ │ │ │ ├── ic_file_html.xml
│ │ │ │ ├── ic_file_mkv.xml
│ │ │ │ ├── ic_file_pdf.xml
│ │ │ │ ├── ic_settings.xml
│ │ │ │ ├── ic_file_rar.xml
│ │ │ │ ├── ic_file_jpg.xml
│ │ │ │ ├── ic_file_mp4.xml
│ │ │ │ ├── ic_file_png.xml
│ │ │ │ ├── ic_file_wav.xml
│ │ │ │ ├── ic_file_mov.xml
│ │ │ │ ├── ic_file_doc.xml
│ │ │ │ ├── ic_file_xls.xml
│ │ │ │ ├── ic_file_jpeg.xml
│ │ │ │ ├── ic_file_wmv.xml
│ │ │ │ ├── ic_file_bmp.xml
│ │ │ │ ├── ic_file_ts.xml
│ │ │ │ ├── ic_file_svg.xml
│ │ │ │ ├── ic_file_mpg.xml
│ │ │ │ ├── ic_file_mp3.xml
│ │ │ │ ├── ic_file_3gp.xml
│ │ │ │ ├── ic_file_wma.xml
│ │ │ │ ├── ic_file_xlsx.xml
│ │ │ │ ├── ic_file_docx.xml
│ │ │ │ ├── ic_file_webm.xml
│ │ │ │ ├── ic_dice.xml
│ │ │ │ └── ic_launcher_2_foreground.xml
│ │ │ ├── color
│ │ │ │ ├── chip_text_color.xml
│ │ │ │ └── bottom_navigation_item.xml
│ │ │ ├── anim
│ │ │ │ ├── fade_in.xml
│ │ │ │ ├── fade_out.xml
│ │ │ │ ├── bottom_fade_scale_in.xml
│ │ │ │ └── bottom_fade_scale_out.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher_1.xml
│ │ │ │ └── ic_launcher_2.xml
│ │ │ ├── layout
│ │ │ │ ├── item_tag.xml
│ │ │ │ ├── popup_resources_tag_menu.xml
│ │ │ │ ├── item_preview_plain_text.xml
│ │ │ │ ├── item_toast.xml
│ │ │ │ ├── layout_progress.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── popup_gallery_tag_menu.xml
│ │ │ │ ├── item_view_folder_tree_device.xml
│ │ │ │ ├── item_image.xml
│ │ │ │ ├── popup_selected_resources_actions.xml
│ │ │ │ ├── item_view_folder_tree_favorite.xml
│ │ │ │ ├── item_boolean_preference.xml
│ │ │ │ ├── fragment_settings.xml
│ │ │ │ ├── dialog_roots_new.xml
│ │ │ │ ├── dialog_sort.xml
│ │ │ │ ├── fragment_folders.xml
│ │ │ │ └── dialog_notification.xml
│ │ │ ├── menu
│ │ │ │ ├── menu_tags_screen.xml
│ │ │ │ └── menu_bottom_navigation.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_baseline_share.xml
│ │ ├── java
│ │ │ └── dev
│ │ │ │ └── arkbuilders
│ │ │ │ └── navigator
│ │ │ │ ├── analytics
│ │ │ │ ├── settings
│ │ │ │ │ ├── SettingsAnalytics.kt
│ │ │ │ │ └── SettingsAnalyticsImpl.kt
│ │ │ │ ├── folders
│ │ │ │ │ ├── FoldersAnalytics.kt
│ │ │ │ │ └── FoldersAnalyticsImpl.kt
│ │ │ │ ├── gallery
│ │ │ │ │ ├── GalleryAnalytics.kt
│ │ │ │ │ └── GalleryAnalyticsImpl.kt
│ │ │ │ ├── Utils.kt
│ │ │ │ ├── resources
│ │ │ │ │ ├── ResourcesAnalytics.kt
│ │ │ │ │ └── ResourcesAnalyticsImpl.kt
│ │ │ │ └── AnalyticsModule.kt
│ │ │ │ ├── presentation
│ │ │ │ ├── navigation
│ │ │ │ │ ├── FragmentForwardAdd.kt
│ │ │ │ │ ├── AppRouter.kt
│ │ │ │ │ └── Screens.kt
│ │ │ │ ├── utils
│ │ │ │ │ ├── ContextUtils.kt
│ │ │ │ │ ├── EditTextExt.kt
│ │ │ │ │ ├── StringProvider.kt
│ │ │ │ │ ├── ToastUtils.kt
│ │ │ │ │ ├── extra
│ │ │ │ │ │ ├── LinkExtraLoader.kt
│ │ │ │ │ │ ├── DocumentExtraLoader.kt
│ │ │ │ │ │ └── ExtraLoader.kt
│ │ │ │ │ └── FullscreenHelper.kt
│ │ │ │ ├── common
│ │ │ │ │ └── CommonMvpView.kt
│ │ │ │ ├── dialog
│ │ │ │ │ ├── sort
│ │ │ │ │ │ ├── SortDialogView.kt
│ │ │ │ │ │ └── SortDialogPresenter.kt
│ │ │ │ │ ├── edittags
│ │ │ │ │ │ └── EditTagsDialogView.kt
│ │ │ │ │ ├── rootsscan
│ │ │ │ │ │ ├── RootsScanView.kt
│ │ │ │ │ │ └── RootsScanDialogPresenter.kt
│ │ │ │ │ ├── ExplainPermsDialog.kt
│ │ │ │ │ ├── InfoDialogFragment.kt
│ │ │ │ │ └── StorageExceptionDialogFragment.kt
│ │ │ │ ├── screen
│ │ │ │ │ ├── gallery
│ │ │ │ │ │ ├── domain
│ │ │ │ │ │ │ └── GalleryItem.kt
│ │ │ │ │ │ └── pager
│ │ │ │ │ │ │ └── PreviewPlainTextViewHolder.kt
│ │ │ │ │ └── resources
│ │ │ │ │ │ ├── adapter
│ │ │ │ │ │ ├── ResourceDiffUtilCallback.kt
│ │ │ │ │ │ └── ResourcesRVAdapter.kt
│ │ │ │ │ │ └── ResourcesView.kt
│ │ │ │ ├── view
│ │ │ │ │ ├── StackedToastsRecyclerView.kt
│ │ │ │ │ ├── KeyListenEditText.kt
│ │ │ │ │ ├── DepthPageTransformer.kt
│ │ │ │ │ ├── LoadingTextView.kt
│ │ │ │ │ └── UserSwitchMaterial.kt
│ │ │ │ └── App.kt
│ │ │ │ ├── data
│ │ │ │ ├── utils
│ │ │ │ │ ├── DevicePathsExtractor.kt
│ │ │ │ │ ├── Popularity.kt
│ │ │ │ │ ├── LogTags.kt
│ │ │ │ │ ├── DevicePathsExtractorImpl.kt
│ │ │ │ │ └── PathExt.kt
│ │ │ │ ├── stats
│ │ │ │ │ ├── StatsStorage.kt
│ │ │ │ │ ├── category
│ │ │ │ │ │ ├── StatsCategoryStorage.kt
│ │ │ │ │ │ ├── TagQueriedTSStorage.kt
│ │ │ │ │ │ ├── TagLabeledTSStorage.kt
│ │ │ │ │ │ └── TagQueriedNStorage.kt
│ │ │ │ │ ├── StatsStorageRepo.kt
│ │ │ │ │ └── AggregatedStatsStorage.kt
│ │ │ │ └── preferences
│ │ │ │ │ └── Preferences.kt
│ │ │ │ └── di
│ │ │ │ └── modules
│ │ │ │ ├── CiceroneModule.kt
│ │ │ │ ├── DispatcherModule.kt
│ │ │ │ └── AppModule.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── dev
│ │ │ └── arkbuilders
│ │ │ └── navigator
│ │ │ ├── stub
│ │ │ ├── StatsStorageStub.kt
│ │ │ ├── TagsStorageStub.kt
│ │ │ ├── MetadataProcessorStub.kt
│ │ │ ├── ResourceIndexStub.kt
│ │ │ └── TestData.kt
│ │ │ └── data
│ │ │ └── utils
│ │ │ ├── DevicePathsExtractorTest.kt
│ │ │ └── PathExtKtTest.kt
│ └── androidTest
│ │ └── java
│ │ └── dev
│ │ └── arkbuilders
│ │ └── navigator
│ │ └── ExampleInstrumentedTest.kt
├── app-scripts
│ └── pre-commit-unix
└── proguard-rules.pro
├── settings.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .github
├── dependabot.yml
├── pull_request_template.md
└── workflows
│ ├── release.yml
│ └── sonar_analysis.yml
├── .gitignore
├── .editorconfig
├── LICENSE
├── gradle.properties
├── CONTRIBUTING.md
└── gradlew.bat
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /libs
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "ARK Navigator"
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ARK-Builders/ARK-Navigator/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_1_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_2_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1C7AE8
4 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gradle" # See documentation for possible values
4 | directory: "/" # Location of package manifests
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea
3 | .gradle
4 | /local.properties
5 | .DS_Store
6 | /build
7 | /buildSrc/build/
8 | *.class
9 | /captures
10 | .externalNativeBuild
11 | .cxx
12 | *.tab
13 | *.tab.values.at
14 | misc.xml
15 | /htmlReport
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/tags_divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rounded_8.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/settings/SettingsAnalytics.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.settings
2 |
3 | interface SettingsAnalytics {
4 | fun trackScreen()
5 | fun trackBooleanPref(name: String, enabled: Boolean)
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/chip_text_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rounded_16dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Jan 08 19:45:49 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/navigation/FragmentForwardAdd.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.navigation
2 |
3 | import ru.terrakok.cicerone.Screen
4 | import ru.terrakok.cicerone.commands.Command
5 |
6 | class FragmentForwardAdd(val screen: Screen) : Command
7 |
--------------------------------------------------------------------------------
/app/src/main/res/color/bottom_navigation_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_dialog_rounded_16dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/folders/FoldersAnalytics.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.folders
2 |
3 | interface FoldersAnalytics {
4 | fun trackScreen()
5 | fun trackRootOpen()
6 | fun trackFavOpen()
7 | fun trackRootAdded()
8 | fun trackFavAdded()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rounded_6_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## :rocket: Summary
2 | Describe things you did in this Pull Request
3 | Issue:
4 |
5 | ## :framed_picture: Screenshots:
6 | Provide screenshots to make it visible to reviewer if possible (Optional)
7 |
8 | | Before | After |
9 | | :---: | :---: |
10 | | Screenshot_1 | Screenshot_2 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rounded_top_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/utils/DevicePathsExtractor.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.utils
2 |
3 | import java.nio.file.Path
4 |
5 | enum class Sorting {
6 | DEFAULT, NAME, SIZE, LAST_MODIFIED, TYPE
7 | }
8 | interface DevicePathsExtractor {
9 | fun listDevices(): List
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/fade_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/fade_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_1.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tag.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/navigation/AppRouter.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.navigation
2 |
3 | import ru.terrakok.cicerone.Router
4 | import ru.terrakok.cicerone.Screen
5 |
6 | class AppRouter : Router() {
7 | fun navigateToFragmentUsingAdd(screen: Screen) {
8 | executeCommands(FragmentForwardAdd(screen))
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_up.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/ContextUtils.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils
2 |
3 | import android.content.Context
4 | import android.util.TypedValue
5 |
6 | fun Context.dpToPx(dp: Float): Float =
7 | TypedValue.applyDimension(
8 | TypedValue.COMPLEX_UNIT_DIP,
9 | dp,
10 | this.resources.displayMetrics
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_down.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_chevron_right_black_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/gallery/GalleryAnalytics.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.gallery
2 |
3 | interface GalleryAnalytics {
4 | fun trackScreen()
5 | fun trackResOpen()
6 | fun trackResShare()
7 | fun trackResInfo()
8 | fun trackResEdit()
9 | fun trackResRemove()
10 | fun trackTagSelect()
11 | fun trackTagRemove()
12 | fun trackTagsEdit()
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/utils/Popularity.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.utils
2 |
3 | class Popularity {
4 | companion object {
5 | fun calculate(elements: List): Map {
6 | val result = mutableMapOf()
7 |
8 | elements.forEach { result[it] = (result[it] ?: 0) + 1 }
9 |
10 | return result.toMap()
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_drag_handle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_sort.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/common/CommonMvpView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.common
2 |
3 | import moxy.MvpView
4 | import moxy.viewstate.strategy.SkipStrategy
5 | import moxy.viewstate.strategy.StateStrategyType
6 | import java.nio.file.Path
7 |
8 | interface CommonMvpView : MvpView {
9 | @StateStrategyType(SkipStrategy::class)
10 | fun toastIndexFailedPath(path: Path)
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_list_add.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_right.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rounded_black_transparent.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_rounded_black_transparent_small.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_action_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/order_ascending.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/order_descending.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_delete_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_tree_node_add.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ripple_simple_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 | -
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_folder.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_tree_node_navigate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/popup_resources_tag_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_explore.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_info.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_push_pin.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/Utils.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics
2 |
3 | import org.matomo.sdk.Tracker
4 | import org.matomo.sdk.extra.TrackHelper
5 |
6 | fun Tracker.trackScreen(build: TrackHelper.() -> TrackHelper.Screen) {
7 | val matomoTracker = this
8 | build(TrackHelper.track()).with(matomoTracker)
9 | }
10 |
11 | fun Tracker.trackEvent(build: TrackHelper.() -> TrackHelper.EventBuilder) {
12 | val matomoTracker = this
13 | build(TrackHelper.track()).with(matomoTracker)
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/dialog/sort/SortDialogView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.dialog.sort
2 |
3 | import moxy.MvpView
4 | import moxy.viewstate.strategy.AddToEndSingleStrategy
5 | import moxy.viewstate.strategy.StateStrategyType
6 | import dev.arkbuilders.navigator.data.utils.Sorting
7 |
8 | @StateStrategyType(AddToEndSingleStrategy::class)
9 | interface SortDialogView : MvpView {
10 | fun init(sorting: Sorting, ascending: Boolean, sortByScoresEnabled: Boolean)
11 | fun closeDialog()
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/bottom_fade_scale_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cloud_on.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/bottom_fade_scale_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/screen/gallery/domain/GalleryItem.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.screen.gallery.domain
2 |
3 | import dev.arkbuilders.arklib.data.index.Resource
4 | import dev.arkbuilders.arklib.data.meta.Metadata
5 | import dev.arkbuilders.arklib.data.preview.PreviewLocator
6 | import java.nio.file.Path
7 |
8 | data class GalleryItem(
9 | val resource: Resource,
10 | val preview: PreviewLocator,
11 | val metadata: Metadata,
12 | val path: Path
13 | ) {
14 | fun id() = resource.id
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_edit_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_vert_dots.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_favorite_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit_file.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_chooser_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_navigate.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_folder_action_navigate.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_search_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/view/StackedToastsRecyclerView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.MotionEvent
6 | import androidx.recyclerview.widget.RecyclerView
7 |
8 | class StackedToastsRecyclerView @JvmOverloads constructor(
9 | context: Context,
10 | attrs: AttributeSet? = null,
11 | defStyleAttr: Int = 0
12 | ) : RecyclerView(context, attrs, defStyleAttr) {
13 |
14 | override fun onInterceptTouchEvent(e: MotionEvent?) = false
15 |
16 | override fun onTouchEvent(e: MotionEvent?) = false
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_preview_plain_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/app-scripts/pre-commit-unix:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | echo "*********************************************************"
3 | echo "Running git pre-commit hook, ktlintCheck in progress..."
4 | echo "*********************************************************"
5 |
6 | ./gradlew ktlintCheck
7 |
8 | status=$?
9 |
10 | if [ "$status" = 0 ] ; then
11 | echo "Static analysis found no problems."
12 | exit 0
13 | else
14 | echo "*********************************************************"
15 | echo 1>&2 "ktlintCheck found violations it could not fix."
16 | echo "Run ./gradlew ktlintFormat to fix formatting related issues..."
17 | echo "*********************************************************"
18 | exit 1
19 | fi
20 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/arkbuilders/navigator/stub/StatsStorageStub.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.stub
2 |
3 | import dev.arkbuilders.arklib.data.stats.StatsEvent
4 | import dev.arkbuilders.navigator.data.stats.StatsStorage
5 | import dev.arkbuilders.arklib.user.tags.Tag
6 |
7 | class StatsStorageStub : StatsStorage {
8 | override suspend fun init() {}
9 |
10 | override fun handleEvent(event: StatsEvent) {}
11 |
12 | override fun statsTagLabeledAmount(): Map = emptyMap()
13 | override fun statsTagQueriedAmount(): Map = emptyMap()
14 |
15 | override fun statsTagQueriedTS(): Map = emptyMap()
16 | override fun statsTagLabeledTS(): Map = emptyMap()
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/arkbuilders/navigator/stub/TagsStorageStub.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.stub
2 |
3 | import dev.arkbuilders.arklib.ResourceId
4 | import dev.arkbuilders.arklib.user.tags.TagStorage
5 | import dev.arkbuilders.arklib.user.tags.Tags
6 |
7 | class TagsStorageStub : TagStorage {
8 | private val tagsById = TestData.tagsById().toMutableMap()
9 |
10 | override fun getValue(id: ResourceId) = tagsById[id]!!
11 |
12 | override suspend fun persist() {}
13 |
14 | override fun remove(id: ResourceId) {
15 | tagsById.remove(id)
16 | }
17 |
18 | override fun setValue(
19 | id: ResourceId,
20 | value: Tags
21 | ) {
22 | tagsById[id] = value
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 | #717171
7 | #50717171
8 | #D3D3D3
9 | #E5E4E2
10 | #FFFFFF
11 | #000000
12 | #A6000000
13 | #99000000
14 | #ADD8E6
15 | #BFFFFFFF
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_cloud_off.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/stats/StatsStorage.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.stats
2 |
3 | import dev.arkbuilders.arklib.data.stats.StatsEvent
4 | import dev.arkbuilders.arklib.user.tags.Tag
5 |
6 | interface StatsStorage {
7 | suspend fun init()
8 | fun handleEvent(event: StatsEvent)
9 | fun statsTagLabeledAmount(): Map
10 | fun statsTagQueriedAmount(): Map
11 | fun statsTagQueriedTS(): Map
12 | fun statsTagLabeledTS(): Map
13 |
14 | companion object {
15 | val TAGS_USAGE_EVENTS = listOf(
16 | StatsEvent.TagsChanged::class.java,
17 | StatsEvent.PlainTagUsed::class.java,
18 | StatsEvent.KindTagUsed::class.java
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/EditTextExt.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils
2 |
3 | import android.view.inputmethod.InputMethodManager
4 | import android.widget.EditText
5 | import androidx.core.content.ContextCompat.getSystemService
6 |
7 | fun EditText.showKeyboard() {
8 | val imm = getSystemService(context, InputMethodManager::class.java)
9 | imm?.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
10 | }
11 |
12 | fun EditText.closeKeyboard() {
13 | val imm = getSystemService(context, InputMethodManager::class.java)
14 | imm?.hideSoftInputFromWindow(this.windowToken, 0)
15 | }
16 |
17 | fun EditText.placeCursorToEnd() {
18 | requestFocus()
19 | post {
20 | setSelection(length())
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/utils/LogTags.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.utils
2 |
3 | object LogTags {
4 | const val MAIN: String = "main"
5 |
6 | const val PERMISSIONS: String = "permissions"
7 |
8 | const val RESOURCES_SCREEN: String = "resources-screen"
9 |
10 | const val GALLERY_SCREEN: String = "gallery-screen"
11 |
12 | const val FOLDERS_SCREEN: String = "folders-screen"
13 |
14 | const val SETTINGS_SCREEN: String = "settings-screen"
15 |
16 | const val FOLDERS_TREE: String = "folders-tree"
17 |
18 | const val TAGS_SELECTOR: String = "tags-selector"
19 |
20 | const val TAGS_STORAGE: String = "tags-storage"
21 |
22 | const val SCORES_STORAGE: String = "scores-storage"
23 |
24 | const val FILES: String = "files"
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/dialog/edittags/EditTagsDialogView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.dialog.edittags
2 |
3 | import moxy.MvpView
4 | import moxy.viewstate.strategy.AddToEndSingleStrategy
5 | import moxy.viewstate.strategy.SkipStrategy
6 | import moxy.viewstate.strategy.StateStrategyType
7 | import dev.arkbuilders.arklib.user.tags.Tag
8 | import dev.arkbuilders.arklib.user.tags.Tags
9 |
10 | @StateStrategyType(AddToEndSingleStrategy::class)
11 | interface EditTagsDialogView : MvpView {
12 | fun init()
13 | fun showKeyboardAndView()
14 | fun hideSortingBtn()
15 | fun setQuickTags(tags: List)
16 | fun setResourceTags(tags: Tags)
17 | fun setInput(input: String)
18 |
19 | @StateStrategyType(SkipStrategy::class)
20 | fun dismissDialog()
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_tags_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
23 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/dev/arkbuilders/navigator/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import junit.framework.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
20 | assertEquals("dev.arkbuilders.navigator", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/settings/SettingsAnalyticsImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.settings
2 |
3 | import dev.arkbuilders.navigator.analytics.trackEvent
4 | import dev.arkbuilders.navigator.analytics.trackScreen
5 | import org.matomo.sdk.Tracker
6 |
7 | class SettingsAnalyticsImpl(
8 | private val matomoTracker: Tracker
9 | ) : SettingsAnalytics {
10 | override fun trackScreen() = matomoTracker.trackScreen { screen(SCREEN_NAME) }
11 |
12 | override fun trackBooleanPref(name: String, enabled: Boolean) {
13 | val enabledStr = if (enabled) "enabled" else "disabled"
14 | matomoTracker
15 | .trackEvent { event(SCREEN_NAME, "$name is $enabledStr") }
16 | }
17 |
18 | companion object {
19 | private const val SCREEN_NAME = "Settings screen"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/di/modules/CiceroneModule.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.di.modules
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import javax.inject.Singleton
6 | import ru.terrakok.cicerone.Cicerone
7 | import ru.terrakok.cicerone.NavigatorHolder
8 | import dev.arkbuilders.navigator.presentation.navigation.AppRouter
9 |
10 | @Module
11 | class CiceroneModule {
12 |
13 | @Singleton
14 | @Provides
15 | fun cicerone(): Cicerone {
16 | return Cicerone.create(AppRouter())
17 | }
18 |
19 | @Provides
20 | fun navigationHolder(cicerone: Cicerone): NavigatorHolder {
21 | return cicerone.navigatorHolder
22 | }
23 |
24 | @Provides
25 | fun router(cicerone: Cicerone): AppRouter {
26 | return cicerone.router
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/view/KeyListenEditText.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.KeyEvent
6 | import com.google.android.material.textfield.TextInputEditText
7 |
8 | class KeyListenEditText(context: Context, attrs: AttributeSet?) :
9 | TextInputEditText(context, attrs) {
10 |
11 | var onBackPressedListener: (() -> Boolean)? = null
12 |
13 | override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
14 | if (keyCode == KeyEvent.KEYCODE_BACK &&
15 | event?.action == KeyEvent.ACTION_DOWN
16 | ) {
17 | onBackPressedListener?.let {
18 | return it()
19 | }
20 | }
21 | return super.onKeyPreIme(keyCode, event)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/radio_button_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | -
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/resources/ResourcesAnalytics.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.resources
2 |
3 | import dev.arkbuilders.arklib.data.storage.StorageException
4 | import dev.arkbuilders.components.tagselector.QueryMode
5 | import dev.arkbuilders.components.tagselector.TagsSorting
6 | import dev.arkbuilders.navigator.data.utils.Sorting
7 |
8 | interface ResourcesAnalytics {
9 | fun trackScreen()
10 | fun trackResClick()
11 | fun trackMoveSelectedRes()
12 | fun trackCopySelectedRes()
13 | fun trackRemoveSelectedRes()
14 | fun trackShareSelectedRes()
15 | fun trackResShuffle()
16 | fun trackTagSortCriteria(tagsSorting: TagsSorting)
17 | fun trackResSortCriteria(sorting: Sorting)
18 | fun trackQueryModeChanged(queryMode: QueryMode)
19 | fun trackStorageProvideException(exception: StorageException)
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/screen/resources/adapter/ResourceDiffUtilCallback.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.screen.resources.adapter
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import dev.arkbuilders.arklib.ResourceId
5 |
6 | class ResourceDiffUtilCallback(
7 | private val oldItems: List,
8 | private val newItems: List
9 | ) : DiffUtil.Callback() {
10 | override fun getOldListSize(): Int = oldItems.size
11 |
12 | override fun getNewListSize(): Int = newItems.size
13 |
14 | override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
15 | return oldItems[oldPos] == newItems[newPos]
16 | }
17 |
18 | // due to content-addressing, `id1 = id2` means `content1 = content2`
19 | override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean =
20 | areItemsTheSame(oldPos, newPos)
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/dialog/rootsscan/RootsScanView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.dialog.rootsscan
2 |
3 | import moxy.MvpView
4 | import moxy.viewstate.strategy.AddToEndSingleStrategy
5 | import moxy.viewstate.strategy.OneExecutionStateStrategy
6 | import moxy.viewstate.strategy.StateStrategyType
7 | import java.nio.file.Path
8 |
9 | @StateStrategyType(AddToEndSingleStrategy::class)
10 | interface RootsScanView : MvpView {
11 | fun init()
12 | fun startScan()
13 | fun scanCompleted(foundRoots: Int)
14 | fun setProgress(foundRoots: Int)
15 |
16 | @StateStrategyType(OneExecutionStateStrategy::class)
17 | fun notifyRootsFound(roots: List)
18 |
19 | @StateStrategyType(OneExecutionStateStrategy::class)
20 | fun toastFolderSkip(folder: Path)
21 |
22 | @StateStrategyType(OneExecutionStateStrategy::class)
23 | fun closeDialog()
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_bottom_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_toast.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/utils/DevicePathsExtractorImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.utils
2 |
3 | import dev.arkbuilders.navigator.presentation.App
4 | import java.nio.file.Path
5 | import javax.inject.Inject
6 |
7 | class DevicePathsExtractorImpl @Inject constructor(
8 | private val appInstance: App
9 | ) : DevicePathsExtractor {
10 |
11 | override fun listDevices(): List =
12 | appInstance
13 | .getExternalFilesDirs(null)
14 | .toList()
15 | .filterNotNull()
16 | .filter { it.exists() }
17 | .map {
18 | it.toPath().toRealPath()
19 | .takeWhile { part ->
20 | part != ANDROID_DIRECTORY
21 | }
22 | .fold(ROOT_PATH) { parent, child ->
23 | parent.resolve(child)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | # noinspection EditorConfigKeyCorrectness
9 | [*]
10 |
11 | indent_style = space
12 | indent_size = 4
13 | tab_width = 4
14 |
15 | disabled_rules=import-ordering
16 |
17 | no-unused-imports = true
18 |
19 | # https://stackoverflow.com/questions/147454/why-is-using-a-wild-card-with-a-java-import-statement-bad
20 | no-wildcard-import = true
21 |
22 | # https://softwareengineering.stackexchange.com/questions/604/is-the-80-character-limit-still-relevant-in-times-of-widescreen-monitors
23 | max_line_length = 85
24 |
25 | # We recommend you to keep these unchanged
26 | end_of_line = lf
27 | charset = utf-8
28 | trim_trailing_whitespace = true
29 | insert_final_newline = true
30 |
31 | [*.md]
32 | trim_trailing_whitespace = false
33 |
34 | [**/test/**.kt]
35 | max_line_length=off
36 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/screen/gallery/pager/PreviewPlainTextViewHolder.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.screen.gallery.pager
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.core.view.GestureDetectorCompat
5 | import androidx.recyclerview.widget.RecyclerView
6 | import dev.arkbuilders.navigator.databinding.ItemPreviewPlainTextBinding
7 |
8 | @SuppressLint("ClickableViewAccessibility")
9 | class PreviewPlainTextViewHolder(
10 | private val binding: ItemPreviewPlainTextBinding,
11 | private val detector: GestureDetectorCompat
12 | ) : RecyclerView.ViewHolder(binding.root) {
13 | var pos = -1
14 |
15 | init {
16 | binding.tvContent.setOnTouchListener { view, event ->
17 | return@setOnTouchListener detector.onTouchEvent(event)
18 | }
19 | }
20 |
21 | fun setContent(text: String) = with(binding) {
22 | tvContent.text = text
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_flv.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_baseline_share.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release the app
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | environment: Development
12 | env:
13 | ACRA_LOGIN: ${{ secrets.ACRARIUM_BASIC_AUTH_LOGIN }}
14 | ACRA_PASS: ${{ secrets.ACRARIUM_BASIC_AUTH_PASSWORD }}
15 | ACRA_URI: ${{ secrets.ACRARIUM_URI }}
16 | BRANCH_NAME: ${{ github.ref_name }}
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | - name: Set up JDK 17
21 | uses: actions/setup-java@v4
22 | with:
23 | java-version: '17'
24 | distribution: 'adopt'
25 |
26 | - name: Validate Gradle wrapper
27 | uses: gradle/actions/wrapper-validation@v3
28 |
29 | - name: Build Release APK
30 | run: ./gradlew assembleRelease
31 |
32 | - name: Release
33 | uses: ncipollo/release-action@v1
34 | with:
35 | artifacts: "./app/build/outputs/apk/release/*.apk"
36 | token: ${{ secrets.GITHUB_TOKEN }}
37 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/arkbuilders/navigator/stub/MetadataProcessorStub.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.stub
2 |
3 | import dev.arkbuilders.arklib.ResourceId
4 | import dev.arkbuilders.arklib.data.meta.Metadata
5 | import dev.arkbuilders.arklib.data.meta.MetadataUpdate
6 | import dev.arkbuilders.arklib.data.processor.RootProcessor
7 |
8 | class MetadataProcessorStub : RootProcessor() {
9 | private val metaById: MutableMap = mapOf(
10 | R1 to Metadata.PlainText(),
11 | R2 to Metadata.Image(),
12 | R3 to Metadata.Video(1, 1, 10),
13 | R4 to Metadata.Archive()
14 | ).toMutableMap()
15 |
16 | override suspend fun init() {}
17 |
18 | override fun retrieve(id: ResourceId): Result =
19 | metaById.mapValues { Result.success(it.value) }
20 | .getOrDefault(id, Result.failure(IllegalArgumentException()))
21 |
22 | override fun forget(id: ResourceId) {
23 | metaById.remove(id)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/StringProvider.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils
2 |
3 | import android.content.Context
4 | import androidx.annotation.StringRes
5 | import dev.arkbuilders.arklib.data.meta.Kind
6 | import dev.arkbuilders.navigator.R
7 |
8 | class StringProvider(private val context: Context) {
9 | fun getString(@StringRes stringResId: Int): String {
10 | return context.getString(stringResId)
11 | }
12 |
13 | fun kindToString(kind: Kind) = when (kind) {
14 | Kind.IMAGE -> context.getString(R.string.kind_image)
15 | Kind.VIDEO -> context.getString(R.string.kind_video)
16 | Kind.DOCUMENT -> context.getString(R.string.kind_document)
17 | Kind.LINK -> context.getString(R.string.kind_link)
18 | Kind.ARCHIVE -> context.getString(R.string.kind_archive)
19 | Kind.PLAINTEXT -> context.getString(R.string.kind_plain_text)
20 | Kind.UNKNOWN -> context.getString(R.string.kind_unknown)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/di/modules/DispatcherModule.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.di.modules
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.Dispatchers
7 | import javax.inject.Qualifier
8 |
9 | @Module
10 | class DispatcherModule {
11 |
12 | @DefaultDispatcher
13 | @Provides
14 | fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
15 |
16 | @IoDispatcher
17 | @Provides
18 | fun providesIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
19 |
20 | @MainDispatcher
21 | @Provides
22 | fun providesMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
23 | }
24 |
25 | @Retention(AnnotationRetention.BINARY)
26 | @Qualifier
27 | annotation class DefaultDispatcher
28 |
29 | @Retention(AnnotationRetention.BINARY)
30 | @Qualifier
31 | annotation class IoDispatcher
32 |
33 | @Retention(AnnotationRetention.BINARY)
34 | @Qualifier
35 | annotation class MainDispatcher
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_zip.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_progress.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_txt.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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
22 |
23 | -dontwarn org.slf4j.impl.StaticLoggerBinder
24 | -dontwarn javax.xml.stream.XMLResolver
25 |
26 | -keep class dev.arkbuilders.arklib.** { *; }
27 | -keep class wseemann.media.FFmpegMediaMetadataRetriever.** { *; }
28 | -keep @kotlinx.serialization.Serializable class * {*;}
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_gif.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021-2022 Kirill Taran
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/ToastUtils.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 | import androidx.annotation.StringRes
6 | import androidx.fragment.app.Fragment
7 | import dev.arkbuilders.navigator.R
8 | import java.nio.file.Path
9 |
10 | fun Context.toast(
11 | @StringRes stringId: Int,
12 | vararg args: Any,
13 | moreTime: Boolean = false
14 | ) {
15 | val duration = if (moreTime) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
16 | Toast.makeText(this, getString(stringId, *args), duration).show()
17 | }
18 |
19 | fun Context.toastFailedPaths(failedPaths: List) {
20 | if (failedPaths.isEmpty()) return
21 | val list = failedPaths.joinToString("\n")
22 | toast(R.string.toast_failed_paths, list, moreTime = true)
23 | }
24 |
25 | fun Fragment.toast(
26 | @StringRes stringId: Int,
27 | vararg args: Any,
28 | moreTime: Boolean = false
29 | ) = requireContext().toast(stringId, *args, moreTime = moreTime)
30 |
31 | fun Fragment.toastFailedPaths(
32 | failedPaths: List
33 | ) = requireContext().toastFailedPaths(failedPaths)
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_avi.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_html.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/dialog/sort/SortDialogPresenter.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.dialog.sort
2 |
3 | import dev.arkbuilders.navigator.data.preferences.PreferenceKey
4 | import dev.arkbuilders.navigator.data.preferences.Preferences
5 | import dev.arkbuilders.navigator.data.utils.Sorting
6 | import kotlinx.coroutines.launch
7 | import moxy.MvpPresenter
8 | import moxy.presenterScope
9 | import javax.inject.Inject
10 |
11 | class SortDialogPresenter : MvpPresenter() {
12 | @Inject
13 | lateinit var preferences: Preferences
14 |
15 | override fun onFirstViewAttach() {
16 | presenterScope.launch {
17 | val sorting = Sorting.values()[preferences.get(PreferenceKey.Sorting)]
18 | val ascending = preferences.get(PreferenceKey.IsSortingAscending)
19 | val sortByScores = preferences.get(PreferenceKey.SortByScores)
20 | viewState.init(sorting, ascending, sortByScores)
21 | }
22 | }
23 |
24 | fun onSortingSelected(sorting: Sorting) = presenterScope.launch {
25 | preferences.set(PreferenceKey.Sorting, sorting.ordinal)
26 | viewState.closeDialog()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/folders/FoldersAnalyticsImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.folders
2 |
3 | import android.content.Context
4 | import dev.arkbuilders.navigator.analytics.trackEvent
5 | import dev.arkbuilders.navigator.analytics.trackScreen
6 | import org.matomo.sdk.Tracker
7 | import javax.inject.Inject
8 |
9 | class FoldersAnalyticsImpl @Inject constructor(
10 | private val matomoTracker: Tracker,
11 | private val context: Context
12 | ) : FoldersAnalytics {
13 |
14 | override fun trackScreen() = matomoTracker
15 | .trackScreen { screen(SCREEN_NAME) }
16 |
17 | override fun trackRootOpen() = matomoTracker.trackScreenEvent("Root opened")
18 |
19 | override fun trackFavOpen() = matomoTracker.trackScreenEvent("Fav opened")
20 |
21 | override fun trackRootAdded() = matomoTracker.trackScreenEvent("Root added")
22 |
23 | override fun trackFavAdded() = matomoTracker.trackScreenEvent("Fav added")
24 |
25 | private fun Tracker.trackScreenEvent(action: String) = this.trackEvent {
26 | event(SCREEN_NAME, action)
27 | }
28 |
29 | companion object {
30 | private const val SCREEN_NAME = "Folders screen"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_mkv.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/arkbuilders/navigator/stub/ResourceIndexStub.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.stub
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.MutableSharedFlow
5 | import dev.arkbuilders.arklib.ResourceId
6 | import dev.arkbuilders.arklib.data.index.Resource
7 | import dev.arkbuilders.arklib.data.index.ResourceIndex
8 | import dev.arkbuilders.arklib.data.index.ResourceUpdates
9 | import dev.arkbuilders.arklib.data.index.RootIndex
10 |
11 | import java.nio.file.Path
12 | import kotlin.io.path.Path
13 |
14 | class ResourceIndexStub : ResourceIndex {
15 | private val resources = TestData.resourceById().toMutableMap()
16 |
17 | override val roots: Set = setOf()
18 |
19 | override val updates: Flow =
20 | MutableSharedFlow()
21 |
22 | override suspend fun updateAll() {}
23 |
24 | override fun allResources(): Map =
25 | resources.toMap()
26 |
27 | override fun getResource(id: ResourceId): Resource? =
28 | resources[id]
29 |
30 | override fun allPaths(): Map =
31 | resources.mapValues { Path("") }
32 |
33 | override fun getPath(id: ResourceId): Path =
34 | Path("")
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/extra/LinkExtraLoader.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils.extra
2 |
3 | import android.widget.TextView
4 | import dev.arkbuilders.navigator.R
5 | import dev.arkbuilders.arklib.data.meta.Metadata
6 | import dev.arkbuilders.navigator.presentation.utils.textOrGone
7 |
8 | object LinkExtraLoader {
9 | fun load(link: Metadata.Link, titleTV: TextView, verbose: Boolean) {
10 | if (!verbose) return
11 | titleTV.textOrGone(link.title)
12 | }
13 |
14 | fun loadWithLabel(
15 | link: Metadata.Link,
16 | titleTV: TextView,
17 | descriptionTV: TextView,
18 | linkTv: TextView
19 | ) {
20 | titleTV.textOrGone(
21 | titleTV.context.getString(
22 | R.string.link_title_label,
23 | link.title
24 | )
25 | )
26 | link.description?.let {
27 | descriptionTV.textOrGone(
28 | descriptionTV.context.getString(
29 | R.string.link_description_label,
30 | it
31 | )
32 | )
33 | }
34 | linkTv.textOrGone(linkTv.context.getString(R.string.link_label))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_pdf.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/extra/DocumentExtraLoader.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils.extra
2 |
3 | import android.widget.TextView
4 | import dev.arkbuilders.navigator.R
5 | import dev.arkbuilders.arklib.data.meta.Metadata
6 | import dev.arkbuilders.navigator.presentation.utils.textOrGone
7 |
8 | object DocumentExtraLoader {
9 | fun load(document: Metadata.Document, pagesTV: TextView, verbose: Boolean) {
10 | val pages = document.pages
11 | if (pages != null) {
12 | val label = when {
13 | verbose -> {
14 | if (pages == 1) {
15 | "$pages page"
16 | } else {
17 | "$pages pages"
18 | }
19 | }
20 | else -> "$pages"
21 | }
22 | pagesTV.textOrGone(label)
23 | }
24 | }
25 |
26 | fun loadWithLabel(
27 | document: Metadata.Document,
28 | tvPageNumber: TextView
29 | ) {
30 | val pages = document.pages
31 | if (pages != null) {
32 | tvPageNumber.textOrGone(
33 | tvPageNumber.context.getString(R.string.doc_page_no_label, pages)
34 | )
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_rar.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_jpg.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_mp4.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_png.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/workflows/sonar_analysis.yml:
--------------------------------------------------------------------------------
1 | name: Analyze the app
2 |
3 | on:
4 | # Trigger analysis when pushing in master or pull requests, and when creating
5 | # a pull request.
6 | push:
7 | branches:
8 | - main
9 | pull_request:
10 | types: [opened, synchronize, reopened]
11 | jobs:
12 | build:
13 | name: SonarQube Analysis
14 | runs-on: ubuntu-latest
15 | permissions: read-all
16 | steps:
17 | - uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
20 | - name: Set up JDK 17
21 | uses: actions/setup-java@v4
22 | with:
23 | java-version: '17'
24 | distribution: 'adopt'
25 | - name: Cache SonarQube packages
26 | uses: actions/cache@v4
27 | with:
28 | path: ~/.sonar/cache
29 | key: ${{ runner.os }}-sonar
30 | restore-keys: ${{ runner.os }}-sonar
31 | - name: Cache Gradle packages
32 | uses: actions/cache@v4
33 | with:
34 | path: ~/.gradle/caches
35 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
36 | restore-keys: ${{ runner.os }}-gradle
37 | - name: Build and analyze
38 | env:
39 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
40 | run: ./gradlew sonar --info
41 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_wav.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_mov.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_doc.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_xls.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/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
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | android.defaults.buildfeatures.buildconfig=true
23 | android.nonTransitiveRClass=false
24 | android.nonFinalResIds=false
25 | android.enableD8.desugaring = true
26 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_jpeg.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_wmv.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/view/DepthPageTransformer.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.view
2 |
3 | import android.view.View
4 | import androidx.viewpager2.widget.ViewPager2
5 | import kotlin.math.abs
6 |
7 | private const val MIN_SCALE = 0.75f
8 |
9 | class DepthPageTransformer() : ViewPager2.PageTransformer {
10 |
11 | override fun transformPage(view: View, position: Float) {
12 | view.apply {
13 | val pageWidth = width
14 | when {
15 | position < -1 -> {
16 | alpha = 0f
17 | }
18 | position <= 0 -> {
19 | alpha = 1f
20 | translationX = 0f
21 | translationZ = 0f
22 | scaleX = 1f
23 | scaleY = 1f
24 | }
25 | position <= 1 -> {
26 | alpha = 1 - position
27 |
28 | translationX = pageWidth * -position
29 | translationZ = -1f
30 |
31 | val scaleFactor =
32 | (MIN_SCALE + (1 - MIN_SCALE) * (1 - abs(position)))
33 | scaleX = scaleFactor
34 | scaleY = scaleFactor
35 | }
36 | else -> {
37 | alpha = 0f
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/arkbuilders/navigator/stub/TestData.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.stub
2 |
3 | import dev.arkbuilders.arklib.ResourceId
4 | import dev.arkbuilders.arklib.data.index.Resource
5 | import java.nio.file.attribute.FileTime
6 | import java.util.Date
7 |
8 | object TestData {
9 | fun resourceById() = mapOf(
10 | R1 to Resource(
11 | R1,
12 | "Resource1",
13 | ".jpg",
14 | fileTime()
15 | ),
16 | R2 to Resource(
17 | R2,
18 | "Resource2",
19 | ".jpg",
20 | fileTime()
21 | ),
22 | R3 to Resource(
23 | R3,
24 | "Resource3",
25 | ".jpg",
26 | fileTime()
27 | ),
28 | R4 to Resource(
29 | R4,
30 | "Resource4",
31 | ".odt",
32 | fileTime()
33 | )
34 | )
35 |
36 | fun tagsById() = mapOf(
37 | R1 to setOf(TAG1, TAG2),
38 | R2 to setOf(TAG2),
39 | R3 to setOf(),
40 | R4 to setOf(TAG3, TAG4)
41 | )
42 |
43 | private fun fileTime() = FileTime.from(Date().toInstant())
44 | }
45 |
46 | val R1 = ResourceId(1L, 1L)
47 | val R2 = ResourceId(2L, 2L)
48 | val R3 = ResourceId(3L, 3L)
49 | val R4 = ResourceId(4L, 4L)
50 |
51 | const val TAG1 = "tag1"
52 | const val TAG2 = "tag2"
53 | const val TAG3 = "tag3"
54 | const val TAG4 = "tag4"
55 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_bmp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/gallery/GalleryAnalyticsImpl.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics.gallery
2 |
3 | import dev.arkbuilders.navigator.analytics.trackEvent
4 | import dev.arkbuilders.navigator.analytics.trackScreen
5 | import org.matomo.sdk.Tracker
6 |
7 | class GalleryAnalyticsImpl(
8 | private val matomoTracker: Tracker
9 | ) : GalleryAnalytics {
10 | override fun trackScreen() = matomoTracker.trackScreen {
11 | screen(SCREEN_NAME)
12 | }
13 |
14 | override fun trackResOpen() = matomoTracker.trackScreenEvent("Resource open")
15 |
16 | override fun trackResShare() = matomoTracker.trackScreenEvent("Resource share")
17 |
18 | override fun trackResInfo() = matomoTracker.trackScreenEvent("Resource info")
19 |
20 | override fun trackResEdit() = matomoTracker.trackScreenEvent("Resource edit")
21 |
22 | override fun trackResRemove() = matomoTracker.trackScreenEvent("Resource remove")
23 |
24 | override fun trackTagSelect() = matomoTracker.trackScreenEvent("Tag select")
25 |
26 | override fun trackTagRemove() = matomoTracker.trackScreenEvent("Tag remove")
27 |
28 | override fun trackTagsEdit() = matomoTracker.trackScreenEvent("Tags edit")
29 |
30 | private fun Tracker.trackScreenEvent(action: String) = this.trackEvent {
31 | event(SCREEN_NAME, action)
32 | }
33 |
34 | companion object {
35 | private const val SCREEN_NAME = "Gallery screen"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_ts.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_svg.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/popup_gallery_tag_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/di/modules/AppModule.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.di.modules
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dev.arkbuilders.navigator.data.preferences.Preferences
7 | import dev.arkbuilders.navigator.data.preferences.PreferencesImpl
8 | import dev.arkbuilders.navigator.data.utils.DevicePathsExtractor
9 | import dev.arkbuilders.navigator.data.utils.DevicePathsExtractorImpl
10 | import dev.arkbuilders.navigator.presentation.App
11 | import dev.arkbuilders.navigator.presentation.utils.StringProvider
12 | import org.matomo.sdk.Matomo
13 | import org.matomo.sdk.Tracker
14 | import org.matomo.sdk.TrackerBuilder
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | class AppModule {
19 |
20 | @Singleton
21 | @Provides
22 | fun stringProvider(ctx: Context): StringProvider {
23 | return StringProvider(ctx)
24 | }
25 |
26 | @Provides
27 | @Singleton
28 | fun provideUserPreferences(ctx: Context): Preferences =
29 | PreferencesImpl(ctx)
30 |
31 | @Provides
32 | @Singleton
33 | fun provideDevicePathsExtractor(application: App): DevicePathsExtractor =
34 | DevicePathsExtractorImpl(application)
35 |
36 | @Provides
37 | @Singleton
38 | fun provideMatomoAnalytics(ctx: Context): Tracker =
39 | TrackerBuilder.createDefault(
40 | "https://ark-builders.matomo.cloud/matomo.php",
41 | 2
42 | ).build(Matomo.getInstance(ctx))
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_mpg.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_mp3.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_view_folder_tree_device.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
27 |
28 |
32 |
33 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_3gp.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/stats/category/StatsCategoryStorage.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.stats.category
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.flow.MutableSharedFlow
5 | import kotlinx.coroutines.flow.debounce
6 | import kotlinx.coroutines.flow.launchIn
7 | import kotlinx.coroutines.flow.onEach
8 | import kotlinx.coroutines.launch
9 | import dev.arkbuilders.arklib.arkFolder
10 | import dev.arkbuilders.arklib.arkStats
11 | import dev.arkbuilders.arklib.data.stats.StatsEvent
12 | import timber.log.Timber
13 | import java.nio.file.Path
14 |
15 | private const val FLUSH_INTERVAL = 10_000L
16 |
17 | abstract class StatsCategoryStorage(
18 | val root: Path,
19 | private val scope: CoroutineScope
20 | ) {
21 | abstract val fileName: String
22 | private val flushFlow = MutableSharedFlow().also { flow ->
23 | flow.debounce(FLUSH_INTERVAL).onEach {
24 | // There may be an exception after root is removed
25 | try {
26 | flush()
27 | } catch (e: Exception) {
28 | Timber.e(e)
29 | }
30 | }.launchIn(scope)
31 | }
32 |
33 | abstract suspend fun init()
34 |
35 | abstract fun handleEvent(event: StatsEvent)
36 | abstract fun provideData(): T
37 | protected abstract fun flush()
38 |
39 | fun locateStorage(): Path? = root.arkFolder().arkStats().resolve(fileName)
40 |
41 | protected fun requestFlush() = scope.launch {
42 | flushFlow.emit(Unit)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/FullscreenHelper.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils
2 |
3 | import android.os.Build
4 | import android.view.Window
5 | import android.view.WindowManager
6 | import androidx.core.view.ViewCompat
7 | import androidx.core.view.WindowInsetsCompat
8 | import androidx.core.view.WindowInsetsControllerCompat
9 |
10 | object FullscreenHelper {
11 |
12 | fun setStatusBarVisibility(isVisible: Boolean, window: Window) =
13 | if (isVisible) showStatusBar(window) else hideStatusBar(window)
14 |
15 | private fun hideStatusBar(window: Window) {
16 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
17 | val windowInsetsController =
18 | ViewCompat.getWindowInsetsController(window.decorView) ?: return
19 | windowInsetsController.systemBarsBehavior =
20 | WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
21 | windowInsetsController.hide(WindowInsetsCompat.Type.statusBars())
22 | } else {
23 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
24 | }
25 | }
26 |
27 | private fun showStatusBar(window: Window) {
28 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
29 | val windowInsetsController = window.insetsController ?: return
30 | windowInsetsController.show(WindowInsetsCompat.Type.statusBars())
31 | } else {
32 | window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_wma.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
29 |
30 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_xlsx.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_docx.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/view/LoadingTextView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.view
2 |
3 | import android.content.Context
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.util.AttributeSet
7 | import androidx.appcompat.widget.AppCompatTextView
8 | import androidx.core.view.isVisible
9 |
10 | class LoadingTextView(context: Context, attrs: AttributeSet?) :
11 | AppCompatTextView(context, attrs) {
12 |
13 | private var isLoading = false
14 | private var isMakingDots = false
15 |
16 | var loadingText: String = ""
17 | set(value) {
18 | field = value
19 | text = loadingText
20 | if (text.isNotEmpty() && !isMakingDots) {
21 | makeLoadingDots()
22 | }
23 | }
24 |
25 | private var dotCount = 0
26 | set(value) {
27 | field = if (value >= 4) {
28 | 0
29 | } else {
30 | value
31 | }
32 | }
33 |
34 | fun setVisibilityAndLoadingStatus(visibility: Int) {
35 | this.visibility = visibility
36 | dotCount = 0
37 | isLoading = isVisible
38 | }
39 |
40 | private fun makeLoadingDots() {
41 | isMakingDots = true
42 | Handler(Looper.getMainLooper()).postDelayed({
43 | if (isLoading) {
44 | dotCount++
45 |
46 | val textToDisplay = "$loadingText${".".repeat(dotCount)}"
47 | this.text = textToDisplay
48 | makeLoadingDots()
49 | } else {
50 | isMakingDots = false
51 | }
52 | }, 500)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/analytics/AnalyticsModule.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.analytics
2 |
3 | import android.content.Context
4 | import dagger.Module
5 | import dagger.Provides
6 | import dev.arkbuilders.navigator.analytics.folders.FoldersAnalytics
7 | import dev.arkbuilders.navigator.analytics.folders.FoldersAnalyticsImpl
8 | import dev.arkbuilders.navigator.analytics.gallery.GalleryAnalytics
9 | import dev.arkbuilders.navigator.analytics.gallery.GalleryAnalyticsImpl
10 | import dev.arkbuilders.navigator.analytics.resources.ResourcesAnalytics
11 | import dev.arkbuilders.navigator.analytics.resources.ResourcesAnalyticsImpl
12 | import dev.arkbuilders.navigator.analytics.settings.SettingsAnalytics
13 | import dev.arkbuilders.navigator.analytics.settings.SettingsAnalyticsImpl
14 | import org.matomo.sdk.Tracker
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | class AnalyticsModule {
19 |
20 | @Singleton
21 | @Provides
22 | fun provideFolderAnalytics(
23 | matomoTracker: Tracker,
24 | context: Context
25 | ): FoldersAnalytics =
26 | FoldersAnalyticsImpl(matomoTracker = matomoTracker, context = context)
27 |
28 | @Singleton
29 | @Provides
30 | fun provideResourcesAnalytics(
31 | matomoTracker: Tracker
32 | ): ResourcesAnalytics = ResourcesAnalyticsImpl(matomoTracker)
33 |
34 | @Singleton
35 | @Provides
36 | fun provideGalleryAnalytics(
37 | matomoTracker: Tracker
38 | ): GalleryAnalytics = GalleryAnalyticsImpl(matomoTracker)
39 |
40 | @Singleton
41 | @Provides
42 | fun provideSettingsAnalytics(
43 | matomoTracker: Tracker
44 | ): SettingsAnalytics = SettingsAnalyticsImpl(matomoTracker)
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_file_webm.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/view/UserSwitchMaterial.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.view
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.widget.CompoundButton
6 | import com.google.android.material.switchmaterial.SwitchMaterial
7 | import dev.arkbuilders.navigator.data.utils.LogTags.SETTINGS_SCREEN
8 | import timber.log.Timber
9 |
10 | class UserSwitchMaterial(
11 | context: Context,
12 | attrs: AttributeSet
13 | ) : SwitchMaterial(context, attrs) {
14 |
15 | private var checkedChangeListener: CustomCheckedChangeListener? = null
16 |
17 | fun setOnUserCheckedChangeListener(
18 | callback: (isChecked: Boolean) -> Unit
19 | ) {
20 | Timber.d(
21 | SETTINGS_SCREEN,
22 | "setOnUserCheckedChangeListener: ${this.id}, " + "$isChecked"
23 | )
24 | if (checkedChangeListener == null) {
25 | checkedChangeListener = CustomCheckedChangeListener(callback)
26 | } else {
27 | checkedChangeListener?.callback = callback
28 | }
29 |
30 | this.setOnCheckedChangeListener(checkedChangeListener)
31 | }
32 |
33 | fun toggleSwitchSilent(mIsChecked: Boolean) {
34 | if (isChecked != mIsChecked) {
35 | isChecked = mIsChecked
36 | jumpDrawablesToCurrentState()
37 | }
38 | }
39 |
40 | private class CustomCheckedChangeListener(
41 | var callback: (isChecked: Boolean) -> Unit
42 | ) : OnCheckedChangeListener {
43 | override fun onCheckedChanged(button: CompoundButton, isChecked: Boolean) {
44 | if (button.isPressed) {
45 | callback(isChecked)
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/stats/StatsStorageRepo.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.stats
2 |
3 | import kotlinx.coroutines.flow.SharedFlow
4 | import dev.arkbuilders.arklib.data.index.ResourceIndex
5 | import dev.arkbuilders.arklib.data.index.RootIndex
6 | import dev.arkbuilders.arklib.data.stats.StatsEvent
7 | import dev.arkbuilders.arklib.user.tags.RootTagsStorage
8 | import dev.arkbuilders.arklib.user.tags.TagsStorageRepo
9 | import dev.arkbuilders.navigator.data.preferences.Preferences
10 | import java.nio.file.Path
11 |
12 | class StatsStorageRepo(
13 | private val tagsStorageRepo: TagsStorageRepo,
14 | private val preferences: Preferences,
15 | private val statsFlow: SharedFlow
16 | ) {
17 | private val storageByRoot = mutableMapOf()
18 |
19 | suspend fun provide(index: ResourceIndex): StatsStorage {
20 | val roots = index.roots
21 |
22 | return if (roots.size > 1) {
23 | val shards = roots.map {
24 | val tagsStorage = tagsStorageRepo.provide(it)
25 | provide(it, tagsStorage)
26 | }
27 |
28 | AggregatedStatsStorage(shards)
29 | } else {
30 | val root = roots.iterator().next()
31 | val tagsStorage = tagsStorageRepo.provide(root)
32 | provide(root, tagsStorage)
33 | }
34 | }
35 |
36 | private suspend fun provide(
37 | root: RootIndex,
38 | tagsStorage: RootTagsStorage
39 | ): PlainStatsStorage =
40 | storageByRoot[root.path] ?: PlainStatsStorage(
41 | root,
42 | preferences,
43 | tagsStorage,
44 | statsFlow
45 | ).also {
46 | it.init()
47 | storageByRoot[root.path] = it
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/utils/PathExt.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.utils
2 |
3 | import java.nio.file.Path
4 | import java.nio.file.Paths
5 | import kotlin.io.path.exists
6 | import kotlin.io.path.extension
7 | import kotlin.io.path.nameWithoutExtension
8 | import kotlin.io.path.notExists
9 |
10 | val ROOT_PATH: Path = Paths.get("/")
11 |
12 | val ANDROID_DIRECTORY: Path = Paths.get("Android")
13 |
14 | fun Path.findNotExistCopyName(name: Path): Path {
15 | val originalNamePath = this.resolve(name.fileName)
16 | if (originalNamePath.notExists()) {
17 | return originalNamePath
18 | }
19 |
20 | var filesCounter = 1
21 |
22 | val formatNameWithCounter =
23 | "${name.nameWithoutExtension}_$filesCounter.${name.extension}"
24 |
25 | var newPath = this.resolve(formatNameWithCounter)
26 |
27 | while (newPath.exists()) {
28 | newPath = this.resolve(formatNameWithCounter)
29 | filesCounter++
30 | }
31 | return newPath
32 | }
33 |
34 | fun findLongestCommonPrefix(paths: List): Path {
35 | if (paths.isEmpty()) {
36 | throw IllegalArgumentException(
37 | "Can't search for common prefix among empty collection"
38 | )
39 | }
40 |
41 | if (paths.size == 1) {
42 | return paths.first()
43 | }
44 |
45 | return tailrec(ROOT_PATH, paths).first
46 | }
47 |
48 | private fun tailrec(prefix: Path, paths: List): Pair> {
49 | val grouped = paths.groupBy { it.getName(0) }
50 | if (grouped.size > 1) {
51 | return prefix to paths
52 | }
53 |
54 | val resolvedPrefix = prefix.resolve(grouped.keys.first())
55 | val shortened = grouped.values.first()
56 | .map { resolvedPrefix.relativize(it) }
57 |
58 | return tailrec(resolvedPrefix, shortened)
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dice.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
16 |
17 |
28 |
29 |
36 |
37 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/screen/resources/ResourcesView.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.screen.resources
2 |
3 | import dev.arkbuilders.components.tagselector.QueryMode
4 | import dev.arkbuilders.navigator.presentation.common.CommonMvpView
5 | import moxy.viewstate.strategy.AddToEndSingleStrategy
6 | import moxy.viewstate.strategy.SkipStrategy
7 | import moxy.viewstate.strategy.StateStrategyType
8 | import java.nio.file.Path
9 |
10 | @StateStrategyType(AddToEndSingleStrategy::class)
11 | interface ResourcesView : CommonMvpView {
12 | fun init(ascending: Boolean, sortByScoresEnabled: Boolean)
13 | fun initResourcesAdapter()
14 | fun updateResourcesAdapter()
15 | fun setProgressVisibility(isVisible: Boolean, withText: String = "")
16 | fun setToolbarTitle(title: String)
17 | fun updateMenu(queryMode: QueryMode)
18 | fun updateOrderBtn(isAscending: Boolean)
19 | fun setSelectingEnabled(enabled: Boolean)
20 | fun setSelectingCount(selected: Int, all: Int)
21 | fun setPreviewGenerationProgress(isVisible: Boolean)
22 | fun setMetadataExtractionProgress(isVisible: Boolean)
23 |
24 | @StateStrategyType(SkipStrategy::class)
25 | fun toastResourcesSelected(selected: Int)
26 |
27 | @StateStrategyType(SkipStrategy::class)
28 | fun toastResourcesSelectedFocusMode(selected: Int, hidden: Int)
29 |
30 | @StateStrategyType(SkipStrategy::class)
31 | fun toastPathsFailed(failedPaths: List)
32 |
33 | @StateStrategyType(SkipStrategy::class)
34 | fun onSelectingChanged(enabled: Boolean)
35 |
36 | @StateStrategyType(SkipStrategy::class)
37 | fun clearStackedToasts()
38 |
39 | @StateStrategyType(SkipStrategy::class)
40 | fun shareResources(resources: List)
41 |
42 | @StateStrategyType(SkipStrategy::class)
43 | fun displayStorageException(label: String, msg: String)
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
24 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/arkbuilders/navigator/data/utils/DevicePathsExtractorTest.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.utils
2 |
3 | import dev.arkbuilders.navigator.presentation.App
4 | import io.mockk.every
5 | import io.mockk.mockk
6 | import io.mockk.verify
7 | import org.junit.jupiter.api.Assertions.assertEquals
8 | import org.junit.jupiter.api.BeforeEach
9 | import org.junit.jupiter.api.Test
10 | import org.junit.runner.RunWith
11 | import org.mockito.junit.MockitoJUnitRunner
12 | import java.io.File
13 | import java.nio.file.Path
14 | import java.nio.file.Paths
15 | import kotlin.io.path.name
16 |
17 | @RunWith(MockitoJUnitRunner::class)
18 | class DevicePathsExtractorTest {
19 |
20 | private val mockedApplication = mockk()
21 |
22 | private lateinit var testee: DevicePathsExtractor
23 |
24 | @BeforeEach
25 | fun setUp() {
26 | testee = DevicePathsExtractorImpl(
27 | appInstance = mockedApplication
28 | )
29 | }
30 |
31 | @Test
32 | fun givenApplicationAvailable_whenGetExternalFileDirs_thenReturnCorrectResult() {
33 | val mockedFile = mockk()
34 | val files = listOf(mockedFile)
35 | every { mockedApplication.getExternalFilesDirs(null) } returns files.toTypedArray()
36 | every { mockedFile.exists() } returns true
37 |
38 | val mockedPath = mockk()
39 | every { mockedFile.toPath() } returns mockedPath
40 | every { mockedPath.toRealPath() } returns mockedPath
41 |
42 | val directoryIterator: MutableIterator = arrayListOf(
43 | Paths.get("PATH")
44 | ).iterator()
45 | every { mockedPath.iterator() } returns directoryIterator
46 |
47 | val result = testee.listDevices()
48 |
49 | verify { mockedApplication.getExternalFilesDirs(null) }
50 | assertEquals(1, result.size)
51 | assertEquals("PATH", result[0].name)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/utils/extra/ExtraLoader.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.utils.extra
2 |
3 | import android.widget.TextView
4 | import dev.arkbuilders.arklib.data.meta.Metadata
5 | import dev.arkbuilders.navigator.presentation.utils.makeGone
6 |
7 | object ExtraLoader {
8 | fun load(meta: Metadata, extraTVs: List, verbose: Boolean) {
9 | extraTVs.forEach { it.makeGone() }
10 |
11 | when (meta) {
12 | is Metadata.Video -> VideoExtraLoader.load(
13 | meta,
14 | extraTVs[0],
15 | extraTVs[1]
16 | )
17 | is Metadata.Document -> DocumentExtraLoader.load(
18 | meta,
19 | extraTVs[0],
20 | verbose
21 | )
22 | is Metadata.Link -> LinkExtraLoader.load(
23 | meta,
24 | extraTVs[1],
25 | verbose
26 | )
27 | else -> {}
28 | }
29 | }
30 |
31 | fun loadWithLabel(
32 | meta: Metadata,
33 | kindPlaceholders: List
34 | ) {
35 | require(kindPlaceholders.size == 3)
36 |
37 | kindPlaceholders.forEach { it.makeGone() }
38 |
39 | when (meta) {
40 | is Metadata.Video -> VideoExtraLoader.loadInfo(
41 | meta,
42 | kindPlaceholders[0],
43 | kindPlaceholders[1]
44 | )
45 | is Metadata.Document -> DocumentExtraLoader.loadWithLabel(
46 | meta,
47 | kindPlaceholders[0]
48 | )
49 | is Metadata.Link -> LinkExtraLoader.loadWithLabel(
50 | meta,
51 | kindPlaceholders[0],
52 | kindPlaceholders[1],
53 | kindPlaceholders[2]
54 | )
55 | else -> {}
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/edit_tags_motion_scene.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
25 |
26 |
31 |
32 |
33 |
34 |
40 |
41 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/popup_selected_resources_actions.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
22 |
23 |
27 |
28 |
32 |
33 |
37 |
38 |
42 |
43 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/presentation/dialog/ExplainPermsDialog.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.presentation.dialog
2 |
3 | import android.content.Context
4 | import android.graphics.Color
5 | import android.graphics.drawable.ColorDrawable
6 | import android.os.Bundle
7 | import android.view.View
8 | import androidx.fragment.app.DialogFragment
9 | import by.kirich1409.viewbindingdelegate.viewBinding
10 | import com.airbnb.lottie.LottieCompositionFactory
11 | import dev.arkbuilders.navigator.R
12 | import dev.arkbuilders.navigator.databinding.DialogExplainPermsBinding
13 | import dev.arkbuilders.navigator.presentation.App
14 | import dev.arkbuilders.navigator.data.PermissionsHelper
15 | import javax.inject.Inject
16 |
17 | class ExplainPermsDialog : DialogFragment(R.layout.dialog_explain_perms) {
18 | private val viewBinding by viewBinding(DialogExplainPermsBinding::bind)
19 |
20 | @Inject
21 | lateinit var permsHelper: PermissionsHelper
22 |
23 | override fun onAttach(context: Context) {
24 | App.instance.appComponent.inject(this)
25 | super.onAttach(context)
26 | }
27 |
28 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
29 | super.onViewCreated(view, savedInstanceState)
30 | dialog?.apply {
31 | window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
32 | setCancelable(false)
33 | setCanceledOnTouchOutside(false)
34 | }
35 | viewBinding.btnAllow.setOnClickListener {
36 | permsHelper.askForWritePermissions(this@ExplainPermsDialog)
37 | dismiss()
38 | }
39 | viewBinding.btnExit.setOnClickListener {
40 | activity?.finish()
41 | }
42 | }
43 |
44 | companion object {
45 | fun newInstance(context: Context): ExplainPermsDialog {
46 | LottieCompositionFactory.fromRawResSync(context, R.raw.anim_file_access)
47 | return ExplainPermsDialog()
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/arkbuilders/navigator/data/stats/category/TagQueriedTSStorage.kt:
--------------------------------------------------------------------------------
1 | package dev.arkbuilders.navigator.data.stats.category
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.serialization.Serializable
5 | import kotlinx.serialization.encodeToString
6 | import kotlinx.serialization.json.Json
7 | import kotlinx.serialization.json.decodeFromStream
8 | import dev.arkbuilders.arklib.data.stats.StatsEvent
9 | import dev.arkbuilders.arklib.user.tags.Tag
10 | import timber.log.Timber
11 | import java.nio.file.Path
12 | import kotlin.io.path.inputStream
13 | import kotlin.io.path.notExists
14 | import kotlin.io.path.writeText
15 |
16 | class TagQueriedTSStorage(
17 | root: Path,
18 | scope: CoroutineScope
19 | ) : StatsCategoryStorage