├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── release_workflow.yml ├── .gitignore ├── .idea ├── CrowdinSettingsPlugin.xml ├── appInsightsSettings.xml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetSelector.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml ├── runConfigurations.xml ├── studiobot.xml └── vcs.xml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── ads ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zs │ │ └── ads │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── zs │ │ └── ads │ │ ├── AdData.kt │ │ ├── AdEventListener.kt │ │ ├── AdManager.kt │ │ ├── AdManagerImpl.kt │ │ ├── AdSize.kt │ │ └── Reward.kt │ └── test │ └── java │ └── com │ └── zs │ └── ads │ └── ExampleUnitTest.kt ├── app ├── .gitignore ├── build.gradle.kts ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── prime │ │ └── media │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── prime │ │ │ └── media │ │ │ ├── Home.kt │ │ │ ├── MainActivity.kt │ │ │ ├── about │ │ │ ├── AboutUs.kt │ │ │ └── MyApps.kt │ │ │ ├── common │ │ │ ├── Backdrop.kt │ │ │ ├── Banner.kt │ │ │ ├── BannerTopBar.kt │ │ │ ├── Chronometer.kt │ │ │ ├── Delegates.kt │ │ │ ├── FloatingActionMenu.kt │ │ │ ├── MetaData.kt │ │ │ ├── Route.kt │ │ │ ├── SelectionTracker.kt │ │ │ ├── SystemFacade.kt │ │ │ ├── Util.kt │ │ │ ├── lazy_ktx.kt │ │ │ └── menu │ │ │ │ └── Action.kt │ │ │ ├── impl │ │ │ ├── AbstractLocalDirViewModel.kt │ │ │ ├── Initializers.kt │ │ │ ├── KoinViewModel.kt │ │ │ ├── PersonalizeViewModel.kt │ │ │ ├── PlaylistViewModel.kt │ │ │ ├── PlaylistsViewModel.kt │ │ │ ├── SettingsViewModel.kt │ │ │ └── VideosViewModel.kt │ │ │ ├── local │ │ │ ├── Albums.kt │ │ │ ├── Artists.kt │ │ │ ├── Directory.kt │ │ │ ├── DirectoryViewState.kt │ │ │ ├── Folders.kt │ │ │ ├── Genres.kt │ │ │ └── videos │ │ │ │ ├── Video.kt │ │ │ │ ├── Videos.kt │ │ │ │ └── VideosViewState.kt │ │ │ ├── old │ │ │ ├── common │ │ │ │ ├── Delegates.kt │ │ │ │ ├── Util.kt │ │ │ │ ├── View.kt │ │ │ │ └── util │ │ │ │ │ ├── DateUtils.kt │ │ │ │ │ ├── PathUtils.kt │ │ │ │ │ └── Util.kt │ │ │ ├── console │ │ │ │ ├── ConsoleView.kt │ │ │ │ ├── Constraints.kt │ │ │ │ ├── Dialogs.kt │ │ │ │ ├── PlayingQueue.kt │ │ │ │ └── ViewState.kt │ │ │ ├── core │ │ │ │ ├── db │ │ │ │ │ └── ContentResolver.kt │ │ │ │ └── playback │ │ │ │ │ ├── Delegates.kt │ │ │ │ │ └── Remote.kt │ │ │ ├── dialog │ │ │ │ └── Properties.kt │ │ │ ├── directory │ │ │ │ ├── Action.kt │ │ │ │ ├── Directory.kt │ │ │ │ ├── DirectoryViewModel.kt │ │ │ │ ├── Metadata.kt │ │ │ │ ├── dialogs │ │ │ │ │ └── Playlists.kt │ │ │ │ ├── playlists │ │ │ │ │ └── Members.kt │ │ │ │ └── store │ │ │ │ │ └── Audios.kt │ │ │ ├── editor │ │ │ │ ├── Editor.kt │ │ │ │ └── ViewState.kt │ │ │ ├── effects │ │ │ │ ├── AudioFx.kt │ │ │ │ └── ViewState.kt │ │ │ ├── feedback │ │ │ │ ├── Feedback.kt │ │ │ │ └── FeedbackViewState.kt │ │ │ ├── impl │ │ │ │ ├── AudioFxViewModel.kt │ │ │ │ ├── ConsoleViewModel.kt │ │ │ │ ├── FeedbackViewModel.kt │ │ │ │ ├── LibraryViewModel.kt │ │ │ │ ├── Remote.kt │ │ │ │ ├── Repository.kt │ │ │ │ ├── SystemDelegate.kt │ │ │ │ └── TagEditorViewModel.kt │ │ │ └── library │ │ │ │ ├── Library.kt │ │ │ │ ├── LibraryStateFulList.kt │ │ │ │ ├── NewlyAddedList.kt │ │ │ │ ├── Promotions.kt │ │ │ │ ├── RecentList.kt │ │ │ │ ├── Shortcuts.kt │ │ │ │ ├── TopAppBar.kt │ │ │ │ └── ViewState.kt │ │ │ ├── personalize │ │ │ ├── FineTune.kt │ │ │ ├── Personalize.kt │ │ │ ├── PersonalizeViewState.kt │ │ │ ├── Tweaks.kt │ │ │ ├── Upgrades.kt │ │ │ └── WIdgets.kt │ │ │ ├── playlists │ │ │ ├── NewPlaylist.kt │ │ │ ├── Playlist.kt │ │ │ ├── PlaylistItem.kt │ │ │ ├── Playlists.kt │ │ │ └── PlaylistsViewState.kt │ │ │ ├── settings │ │ │ ├── Settings.kt │ │ │ └── SettingsViewState.kt │ │ │ └── widget │ │ │ ├── DiskDynamo.kt │ │ │ ├── ElongatedBeat.kt │ │ │ ├── Glance.kt │ │ │ ├── GoldenDust.kt │ │ │ ├── GradientGroves.kt │ │ │ ├── Iphone.kt │ │ │ ├── MiniLayout.kt │ │ │ ├── MistyDream.kt │ │ │ ├── RedVioletCake.kt │ │ │ ├── RotatingGradient.kt │ │ │ ├── SkewedDynamic.kt │ │ │ ├── SnowCone.kt │ │ │ ├── Tiramisu.kt │ │ │ └── WavyGradeintDots.kt │ └── res │ │ ├── drawable-v24 │ │ ├── ic_launcher_foreground.xml │ │ └── ic_splash.xml │ │ ├── drawable │ │ ├── avd_repeat_more_one_all.xml │ │ ├── default_art.png │ │ ├── ic_artist.xml │ │ ├── ic_branded_image.png │ │ ├── ic_remove_ads.xml │ │ ├── ic_splash.png │ │ ├── media3_notification_pause.xml │ │ └── media3_notification_play.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── raw │ │ ├── bg_blur_color_blob.lottie │ │ ├── bg_dark_wavy_lines.lottie │ │ ├── bg_fading_streight_lines.lottie │ │ ├── bg_gradeint_dots.lottie │ │ ├── bg_rotating_color_gradient.lottie │ │ ├── loading_hand.lottie │ │ ├── lt_audio_waves.json │ │ ├── lt_bg_baloon_in_air.lottie │ │ ├── lt_bg_blur.lottie │ │ ├── lt_bg_fluid_color.lottie │ │ ├── lt_bg_snowflakes.lottie │ │ ├── lt_color_loader.json │ │ ├── lt_empty_box.json │ │ ├── lt_golden_ticket.lottie │ │ ├── lt_green_list_loading.lottie │ │ ├── lt_list_loading_grey.lottie │ │ ├── lt_list_loading_shimmer_2.lottie │ │ ├── lt_loading_bubbles.json │ │ ├── lt_loading_dots_blue.json │ │ ├── lt_permission.json │ │ ├── lt_play_pause.json │ │ ├── lt_play_pause2.json │ │ ├── lt_play_pause3.json │ │ ├── lt_play_pause4.json │ │ ├── lt_play_pause5.json │ │ ├── lt_play_pause6.json │ │ ├── lt_play_pause7.json │ │ ├── lt_play_pause8.json │ │ ├── lt_play_pause9.json │ │ ├── lt_play_pause_circle_bordered.json │ │ ├── lt_rounded_next_btn.json │ │ ├── lt_settings_roll.json │ │ ├── lt_shuffle_on_off.json │ │ ├── lt_skip_to_next.json │ │ ├── lt_skip_to_next_circular_bordered.json │ │ ├── lt_twitter_heart_filled_unfilled.json │ │ ├── play_pause_circle_bordered.lottie │ │ ├── play_pause_filled.lottie │ │ └── playback_indicator.json │ │ ├── values-af-rZA │ │ └── strings.xml │ │ ├── values-ar-rSA │ │ └── strings.xml │ │ ├── values-ca-rES │ │ └── strings.xml │ │ ├── values-cs-rCZ │ │ └── strings.xml │ │ ├── values-da-rDK │ │ └── strings.xml │ │ ├── values-de-rDE │ │ └── strings.xml │ │ ├── values-el-rGR │ │ └── strings.xml │ │ ├── values-es-rES │ │ └── strings.xml │ │ ├── values-fi-rFI │ │ └── strings.xml │ │ ├── values-fr-rFR │ │ └── strings.xml │ │ ├── values-hi-rIN │ │ └── strings.xml │ │ ├── values-hu-rHU │ │ └── strings.xml │ │ ├── values-it-rIT │ │ └── strings.xml │ │ ├── values-iw-rIL │ │ └── strings.xml │ │ ├── values-ja-rJP │ │ └── strings.xml │ │ ├── values-ko-rKR │ │ └── strings.xml │ │ ├── values-nl-rNL │ │ └── strings.xml │ │ ├── values-no-rNO │ │ └── strings.xml │ │ ├── values-pl-rPL │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ └── strings.xml │ │ ├── values-pt-rPT │ │ └── strings.xml │ │ ├── values-ro-rRO │ │ └── strings.xml │ │ ├── values-ru-rRU │ │ └── strings.xml │ │ ├── values-sr-rSP │ │ └── strings.xml │ │ ├── values-sv-rSE │ │ └── strings.xml │ │ ├── values-tr-rTR │ │ └── strings.xml │ │ ├── values-uk-rUA │ │ └── strings.xml │ │ ├── values-ur-rPK │ │ └── strings.xml │ │ ├── values-vi-rVN │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ └── values │ │ ├── font_certs.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ ├── theme.xml │ │ └── what_s_new.xml │ └── test │ └── java │ └── com │ └── prime │ └── media │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── codex ├── .gitignore ├── build.gradle.kts └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── prime │ │ └── codex │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── prime │ │ └── codex │ │ └── Codex.kt │ └── test │ └── java │ └── com │ └── prime │ └── codex │ └── ExampleUnitTest.kt ├── core-ui ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zs │ │ └── core_ui │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── zs │ │ └── core_ui │ │ ├── AppTheme.kt │ │ ├── BackgroundPainter.kt │ │ ├── CollapsableNeumorphicLargeAppBar.kt │ │ ├── ColorPicker.kt │ │ ├── Delegates.kt │ │ ├── LazyList.kt │ │ ├── NightMode.kt │ │ ├── PagerIndicator.kt │ │ ├── ScaleIndication.kt │ │ ├── ToggleButton.kt │ │ ├── Utils.kt │ │ ├── WallpaperAccentColor.kt │ │ ├── WindowSize.kt │ │ ├── WindowStyle.kt │ │ ├── adaptive │ │ ├── NavigationItem.kt │ │ ├── NavigationSuiteScaffold.kt │ │ ├── TwoPane.kt │ │ └── TwoPaneStrategy.kt │ │ ├── coil │ │ ├── MediaMetaDataArtFetcher.kt │ │ ├── ReBlurTransformation.kt │ │ ├── RsBlurTransformation.kt │ │ └── VideoThumbnailFetcher.kt │ │ ├── shape │ │ ├── CloudShape.kt │ │ ├── CompactDisk.kt │ │ ├── EndConcaveShape.kt │ │ ├── Folder.kt │ │ ├── HeartShape.kt │ │ ├── NotichedCornorShape.kt │ │ ├── RoundedPolygonShape.kt │ │ ├── RoundedStarShape.kt │ │ ├── SkewedRoundedRectangleShape.kt │ │ ├── TagShape.kt │ │ └── TopConcaveShape.kt │ │ ├── shimmer │ │ ├── Highlight.kt │ │ └── Shimmer.kt │ │ └── toast │ │ ├── Helpers.kt │ │ ├── Toast.kt │ │ └── ToastHostState.kt │ └── test │ └── java │ └── com │ └── zs │ └── core_ui │ └── ExampleUnitTest.kt ├── core ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── zs │ │ └── core │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── zs │ │ └── core │ │ ├── Temp.kt │ │ ├── Util.kt │ │ ├── db │ │ ├── Playlists.kt │ │ ├── Playlists2.kt │ │ └── Realm.kt │ │ ├── paymaster │ │ ├── Paymaster.kt │ │ ├── PaymasterImpl.kt │ │ ├── Purchase.kt │ │ └── Security.java │ │ ├── playback │ │ ├── MediaItem.kt │ │ ├── NowPlaying.kt │ │ ├── Playback.kt │ │ ├── PlaybackController.kt │ │ ├── PlaybackControllerImpl.kt │ │ └── Util.kt │ │ ├── store │ │ ├── MediaProvider.kt │ │ ├── MediaProviderImpl.kt │ │ ├── Model.kt │ │ └── resolver-ktx.kt │ │ └── util │ │ ├── PathUtil.kt │ │ └── Util.kt │ └── test │ └── java │ └── com │ └── zs │ └── core │ └── ExampleUnitTest.kt ├── crowdin.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts ├── stability_config.conf └── widget ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── zs │ └── widget │ └── ExampleInstrumentedTest.kt ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── zs │ │ └── widget │ │ ├── AppWidget.kt │ │ ├── Chronometer.kt │ │ ├── Universal.kt │ │ ├── Util.kt │ │ └── ViewType.kt └── res │ ├── drawable │ ├── app_widget_preview.webp │ ├── ic_config.xml │ ├── ic_pause_rounded_filled.xml │ ├── ic_round_play_filled.xml │ ├── ic_skip_to_next.xml │ ├── ic_skip_to_prev.xml │ ├── media3_notification_pause.xml │ ├── media3_notification_play.xml │ └── rect_rounded_cornors_12dp.xml │ ├── layout │ ├── chronometer.xml │ └── chronometer_bold.xml │ └── xml │ └── app_widget_info.xml └── test └── java └── com └── zs └── widget └── ExampleUnitTest.kt /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | app/src/main/java/com/prime/media/core/billing/Private.kt 17 | .kotlin 18 | -------------------------------------------------------------------------------- /.idea/CrowdinSettingsPlugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 45 | 46 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetSelector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/studiobot.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Sure! Here's an example of a simplified version of contributing guidelines for an app in the alpha stage: 2 | 3 | # Contributing Guidelines (Alpha Stage) 4 | 5 | Thank you for your interest in our app! We are currently in the alpha stage, which means that many things are still being developed and changed. At this time, we are not accepting external contributions. However, we appreciate your support and encourage you to stay tuned for future updates. 6 | 7 | ## Providing Feedback 8 | 9 | As we continue to refine and improve our app, we value your feedback. If you encounter any issues, have suggestions, or want to share your thoughts, please feel free to reach out to us through [contact information or link to feedback channels]. Your input is essential in helping us shape the future of the app. 10 | 11 | ## Stay Updated 12 | 13 | To stay informed about the latest updates, releases, and opportunities to contribute in the future, we recommend: 14 | 15 | 1. Following our official channels, such as our website, blog, and social media accounts. 16 | 2. Joining our mailing list, if available, to receive important announcements. 17 | 3. Keeping an eye on our GitHub repository for any future changes to the contributing guidelines. 18 | 19 | Thank you for your understanding and patience as we work towards improving the app. We look forward to your continued support! 20 | 21 | Please note that these guidelines are specific to the alpha stage of your app, and you can modify them based on your project's needs and the stage of development. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | The following table provides information about the versions of our project currently being supported with security updates: 6 | 7 | | Version | Supported | 8 | | ------- | ------------------ | 9 | | 1.0.x | :white_check_mark: | 10 | | < 1.0 | :x: | 11 | 12 | Please note that only the version 1.0.x is actively supported with security updates. It is crucial to keep your project up to date with the latest version to ensure you receive necessary security patches and fixes. 13 | 14 | ## Reporting a Vulnerability 15 | 16 | If you discover a vulnerability within our project, we encourage you to report it promptly. To report a vulnerability, please follow these steps: 17 | 18 | 1. Visit our GitHub repository at [https://github.com/prime-zs/Audiofy2](https://github.com/prime-zs/Audiofy2). 19 | 20 | 2. Create a new issue in the repository by clicking on the "Issues" tab and then the "New Issue" button. 21 | 22 | 3. Provide a descriptive title for the issue, summarizing the vulnerability in a concise manner. 23 | 24 | 4. In the issue description, include the following information: 25 | - A detailed explanation of the vulnerability, including steps to reproduce it and any relevant technical details. 26 | - Information about the affected version(s) of the project. 27 | - If applicable, suggestions or recommendations for mitigating the vulnerability. 28 | 29 | 5. Submit the issue and our security team will review it promptly. 30 | 31 | We will keep you informed about the progress of the vulnerability assessment and subsequent actions taken through GitHub issue comments. You can expect regular updates on the status of your report. 32 | 33 | We appreciate your contribution to our project's security and value your efforts in helping us maintain a robust and secure system. Thank you for your cooperation! 34 | 35 | 36 | -------------------------------------------------------------------------------- /ads/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ads/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.zs.ads" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | 16 | buildTypes { 17 | release { 18 | isMinifyEnabled = false 19 | proguardFiles( 20 | getDefaultProguardFile("proguard-android-optimize.txt"), 21 | "proguard-rules.pro" 22 | ) 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility = JavaVersion.VERSION_1_8 27 | targetCompatibility = JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { jvmTarget = "1.8" } 30 | } 31 | 32 | dependencies { implementation(libs.bundles.ad.network) } -------------------------------------------------------------------------------- /ads/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/ads/consumer-rules.pro -------------------------------------------------------------------------------- /ads/src/androidTest/java/com/zs/ads/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.ads 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.zs.ads.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /ads/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ads/src/main/java/com/zs/ads/AdEventListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 29-07-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.ads 20 | 21 | 22 | /** 23 | * An interface for listening to ad events. 24 | * 25 | * Implement this interface to receive notifications about various ad events, 26 | * such asad loading, impressions, clicks, and errors. 27 | */ 28 | interface AdEventListener { 29 | 30 | /** 31 | * Called when an ad event occurs. 32 | * 33 | * @param event The name of the ad event that occurred. 34 | * @param data An optional [AdData] object containing additional information about the event, 35 | * such as ad metadata or error details. 36 | */ 37 | fun onAdEvent(event: String, data: AdData?) 38 | 39 | /** 40 | * Called when an ad impression event occurs. This can be triggered when: 41 | * - A banner ad is successfully loaded. 42 | * - or the impression listener records impression 43 | * 44 | * @param value An [AdData.AdImpression] object containing details about the ad impression, 45 | * or null if no ad impression data is available. 46 | */ 47 | fun onAdImpression(value: AdData.AdImpression?) 48 | 49 | /** 50 | * Called when a rewarded ad is successfully completed and the user earns a reward. 51 | * 52 | * @param reward A [Reward] object representing the reward earned, or null if no reward is provided. 53 | * @param info An [AdData.AdInfo] object containing additional information about the ad event. 54 | */ 55 | fun onAdRewarded(reward: Reward?, info: AdData.AdInfo?) 56 | } -------------------------------------------------------------------------------- /ads/src/main/java/com/zs/ads/AdSize.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 02-07-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.ads 20 | 21 | import com.ironsource.mediationsdk.ISBannerSize 22 | 23 | /** 24 | * Represents the size of an ad banner. 25 | ** @property size The underlying [ISBannerSize] object. 26 | */ 27 | @JvmInline 28 | value class AdSize private constructor(internal val value: ISBannerSize) { 29 | 30 | /** 31 | * Creates a custom ad size. 32 | * 33 | * @param type The type of the custom ad size (default is "CUSTOM"). 34 | * @param width The width of the ad in pixels. 35 | * @param height The height of the ad in pixels. 36 | */ 37 | constructor(width: Int, height: Int, type: String = "CUSTOM") : this( 38 | ISBannerSize( 39 | type, 40 | width, 41 | height 42 | ) 43 | ) 44 | 45 | companion object { 46 | /** 47 | * Standard banner size (320x50). 48 | */ 49 | val BANNER = AdSize(ISBannerSize.BANNER) 50 | 51 | /** 52 | * Large banner size (320x100).*/ 53 | val LARGE_BANNER = AdSize(ISBannerSize.LARGE) 54 | 55 | /** 56 | * Medium rectangle size (300x250). 57 | */ 58 | val MEDIUM_RECTANGLE = AdSize(ISBannerSize.RECTANGLE) 59 | 60 | /** 61 | * Smart banner size that adjusts to the device's width. 62 | */ 63 | val SMART = AdSize(ISBannerSize.SMART) 64 | 65 | /** 66 | * Leaderboard size (728x90). 67 | */ 68 | val LEADERBOARD = AdSize(728, 90, "LEADERBOARD",) 69 | 70 | /** 71 | * Full banner size (468x60). 72 | */ 73 | val FULL_BANNER = AdSize(468, 60, "FULL_BANNER",) 74 | } 75 | } -------------------------------------------------------------------------------- /ads/src/main/java/com/zs/ads/Reward.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 31-07-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.ads 20 | 21 | import com.ironsource.mediationsdk.model.Placement 22 | 23 | 24 | /** 25 | * A value class representing a reward earned from an ad placement. 26 | * 27 | * @property value The underlying [Placement] object that holds the reward details. 28 | * @property amount The amount of the reward. 29 | * @property name The name or type of the reward. 30 | */ 31 | @JvmInline 32 | value class Reward(internal val value: Placement){ 33 | val amount get() = value.rewardAmount 34 | val name get() = value.rewardName 35 | } -------------------------------------------------------------------------------- /ads/src/test/java/com/zs/ads/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.ads 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "395273193376", 4 | "project_id": "audiofy-4ce6c", 5 | "storage_bucket": "audiofy-4ce6c.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:395273193376:android:1bffdeb26f98ce4a0e46c5", 11 | "android_client_info": { 12 | "package_name": "com.prime.mediaplayer" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "395273193376-25rhk3332e3gntapskvshc4lv3g0jm03.apps.googleusercontent.com", 18 | "client_type": 3 19 | } 20 | ], 21 | "api_key": [ 22 | { 23 | "current_key": "AIzaSyBKeIYrSFPr7tbeSiAj07kHWOUG0w3fFRg" 24 | } 25 | ], 26 | "services": { 27 | "appinvite_service": { 28 | "other_platform_oauth_client": [ 29 | { 30 | "client_id": "395273193376-25rhk3332e3gntapskvshc4lv3g0jm03.apps.googleusercontent.com", 31 | "client_type": 3 32 | } 33 | ] 34 | } 35 | } 36 | }, 37 | { 38 | "client_info": { 39 | "mobilesdk_app_id": "1:395273193376:android:d177d81aff07ac610e46c5", 40 | "android_client_info": { 41 | "package_name": "com.prime.player" 42 | } 43 | }, 44 | "oauth_client": [ 45 | { 46 | "client_id": "395273193376-25rhk3332e3gntapskvshc4lv3g0jm03.apps.googleusercontent.com", 47 | "client_type": 3 48 | } 49 | ], 50 | "api_key": [ 51 | { 52 | "current_key": "AIzaSyBKeIYrSFPr7tbeSiAj07kHWOUG0w3fFRg" 53 | } 54 | ], 55 | "services": { 56 | "appinvite_service": { 57 | "other_platform_oauth_client": [ 58 | { 59 | "client_id": "395273193376-25rhk3332e3gntapskvshc4lv3g0jm03.apps.googleusercontent.com", 60 | "client_type": 3 61 | } 62 | ] 63 | } 64 | } 65 | }, 66 | { 67 | "client_info": { 68 | "mobilesdk_app_id": "1:395273193376:android:d177d81aff07ac610e46c5", 69 | "android_client_info": { 70 | "package_name": "com.prime.player.debug" 71 | } 72 | }, 73 | "oauth_client": [ 74 | { 75 | "client_id": "395273193376-25rhk3332e3gntapskvshc4lv3g0jm03.apps.googleusercontent.com", 76 | "client_type": 3 77 | } 78 | ], 79 | "api_key": [ 80 | { 81 | "current_key": "AIzaSyBKeIYrSFPr7tbeSiAj07kHWOUG0w3fFRg" 82 | } 83 | ], 84 | "services": { 85 | "appinvite_service": { 86 | "other_platform_oauth_client": [ 87 | { 88 | "client_id": "395273193376-25rhk3332e3gntapskvshc4lv3g0jm03.apps.googleusercontent.com", 89 | "client_type": 3 90 | } 91 | ] 92 | } 93 | } 94 | } 95 | ], 96 | "configuration_version": "1" 97 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/prime/media/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.prime.player", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/common/Chronometer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 05-01-2025. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.common 20 | 21 | import androidx.compose.runtime.MutableLongState 22 | import androidx.compose.runtime.mutableLongStateOf 23 | 24 | /** 25 | * A composable chronometer that tracks elapsed time. 26 | * 27 | * This class provides a way to manage and display a time duration, similar to a stopwatch. 28 | * It uses a [MutableLongState] internally to hold the current time value, allowing for 29 | * manual setting and retrieval of the elapsed time. 30 | * 31 | * The chronometer's value represents the elapsed time in milliseconds. 32 | * 33 | * @property value The current elapsed time in milliseconds. This property can be used to 34 | * get or set the chronometer's current value. 35 | */ 36 | @JvmInline 37 | value class Chronometer private constructor(private val state: MutableLongState){ 38 | 39 | /** 40 | * Constructs a [Chronometer] with an initial time value. 41 | * 42 | * @param initial The initial time value in milliseconds. 43 | */ 44 | constructor(initial: Long): this(mutableLongStateOf(initial)) 45 | 46 | /** 47 | * Gets or sets the current elapsed time in milliseconds. 48 | * 49 | * When setting the value, the chronometer's internal state is updated to reflect the new time. 50 | * When getting the value, the current elapsed time is returned. 51 | */ 52 | var value get() = state.longValue 53 | set(value){ 54 | state.longValue = value 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/common/FloatingActionMenu.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 20-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.common 20 | 21 | import androidx.compose.animation.animateContentSize 22 | import androidx.compose.foundation.BorderStroke 23 | import androidx.compose.foundation.layout.Arrangement 24 | import androidx.compose.foundation.layout.Row 25 | import androidx.compose.foundation.layout.RowScope 26 | import androidx.compose.foundation.shape.CircleShape 27 | import androidx.compose.material.Surface 28 | import androidx.compose.runtime.Composable 29 | import androidx.compose.runtime.NonRestartableComposable 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.draw.scale 33 | import androidx.compose.ui.unit.dp 34 | import com.zs.core_ui.AppTheme 35 | 36 | private val DefaultItemSpace = Arrangement.spacedBy(AppTheme.padding.small) 37 | 38 | /** 39 | * A composable function that creates a floating action menu. 40 | * 41 | * @param modifier The modifier to be applied to the layout. 42 | * @param content The composable content to be displayed within the menu. 43 | */ 44 | @Composable 45 | @NonRestartableComposable 46 | fun FloatingActionMenu( 47 | modifier: Modifier = Modifier, 48 | content: @Composable RowScope.() -> Unit 49 | ) { 50 | Surface( 51 | modifier = modifier.scale(0.85f), 52 | color = AppTheme.colors.background(elevation = 2.dp), 53 | contentColor = AppTheme.colors.onBackground, 54 | shape = CircleShape, 55 | border = BorderStroke(1.dp, AppTheme.colors.background(elevation = 4.dp)), 56 | elevation = 12.dp, 57 | content = { 58 | Row( 59 | horizontalArrangement = DefaultItemSpace, 60 | verticalAlignment = Alignment.CenterVertically, 61 | modifier = Modifier.animateContentSize(), 62 | content = content 63 | ) 64 | } 65 | ) 66 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/common/SelectionTracker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 24-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.common 20 | 21 | import androidx.compose.runtime.State 22 | 23 | /** 24 | * Provides a way to track selection state. 25 | * Each property is observable. 26 | * 27 | * @property focused The key of the currently focused item. Null if none is focused. 28 | * @property selected The list of keys representing the currently selected items. 29 | * @property isInSelectionMode Indicates whether selection mode is active. 30 | * @property allSelected True if all items are selected; otherwise, false. 31 | */ 32 | interface SelectionTracker { 33 | 34 | /** 35 | * Represents the selection level of a group. 36 | */ 37 | enum class Level { NONE, PARTIAL, FULL } 38 | 39 | var focused: String? 40 | val selected: List 41 | val isInSelectionMode: Boolean 42 | val allSelected: Boolean 43 | 44 | /** 45 | * Returns the selection level of the group represented by the key. 46 | * 47 | * @param key The key representing the group. 48 | * @return A `State` object representing the selection level of the group. 49 | * @throws UnsupportedOperationException If this function is not supported by the implementing class. 50 | */ 51 | fun isGroupSelected(key: String): State { 52 | throw UnsupportedOperationException("This function is not supported by ${this::class.java.simpleName}") 53 | } 54 | 55 | /** 56 | * Toggles the selection of the group represented by the key. 57 | * 58 | * @param key The key representing the group. 59 | * @throws UnsupportedOperationException If this function is not supported by the implementing class. 60 | */ 61 | fun check(key: String) { 62 | throw UnsupportedOperationException("This function is not supported by ${this::class.java.simpleName}") 63 | } 64 | 65 | /** 66 | * Toggles the selection of the item having the specified key. 67 | * 68 | * @param key The key representing the item. 69 | */ 70 | fun select(key: String) 71 | 72 | /** 73 | * Clears the selection. 74 | */ 75 | fun clear() 76 | 77 | /** 78 | * Selects all items. 79 | */ 80 | fun selectAll() 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/common/menu/Action.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 24-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.common.menu 20 | 21 | import androidx.annotation.StringRes 22 | import androidx.compose.ui.graphics.vector.ImageVector 23 | 24 | /** 25 | * Represents a single item within a menu. The [label] represents the id of this item as well. 26 | * 27 | * @property label The text label displayed for the menu item. 28 | * Can include a subtitle on a new line, up to two lines maximum. 29 | * @property icon Optional icon associated with the menu item. 30 | * @property enabled Indicates whether the menu item is currently enabled and interactive. 31 | */ 32 | interface Action { 33 | @get:StringRes val label: Int 34 | val id: String 35 | val icon: ImageVector? 36 | val enabled: Boolean 37 | 38 | /** 39 | * Creates a copy of this [MenuItem] with the specified modifications. 40 | * 41 | * @param title The text label for the new menu item. 42 | * @param icon The icon for the new menu item. 43 | * @param enabled Whether the new menu item is enabled. 44 | * @return A new [MenuItem] instance with the applied modifications. 45 | */ 46 | fun copy( 47 | enabled: Boolean = this.enabled, 48 | ): Action = ActionImpl(label, id, icon, enabled) 49 | 50 | companion object { 51 | 52 | /** 53 | * @see MenuItem 54 | */ 55 | operator fun invoke( 56 | @StringRes label: Int, 57 | icon: ImageVector? = null, 58 | enabled: Boolean = true, 59 | id: String = label.toString() 60 | ): Action = ActionImpl(label, id, icon, enabled) 61 | } 62 | } 63 | 64 | /** 65 | * Default implementation of [MenuItem]. 66 | */ 67 | private class ActionImpl( 68 | override val label: Int , 69 | override val id: String, 70 | override val icon: ImageVector?, 71 | override val enabled: Boolean 72 | ) : Action { 73 | override fun equals(other: Any?): Boolean { 74 | if (this === other) return true 75 | if (javaClass != other?.javaClass) return false 76 | 77 | other as ActionImpl 78 | 79 | return id == other.id 80 | } 81 | 82 | override fun hashCode(): Int { 83 | return id.hashCode() 84 | } 85 | 86 | override fun toString(): String { 87 | return "Action(Label = $label, id = $id, icon = $icon, enabled = $enabled)" 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/impl/PersonalizeViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 17-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.impl 20 | 21 | import androidx.compose.ui.graphics.Shape 22 | import com.google.firebase.analytics.FirebaseAnalytics 23 | import com.google.firebase.analytics.ktx.analytics 24 | import com.google.firebase.analytics.logEvent 25 | import com.google.firebase.ktx.Firebase 26 | import com.prime.media.BuildConfig 27 | import com.prime.media.personalize.PersonalizeViewState 28 | import com.prime.media.settings.Settings 29 | import com.primex.preferences.Key 30 | 31 | class PersonalizeViewModel : KoinViewModel(), PersonalizeViewState { 32 | override fun setInAppWidget(id: String) { 33 | preferences[Settings.GLANCE] = id 34 | Firebase.analytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM) { 35 | param(FirebaseAnalytics.Param.ITEM_ID, id) 36 | } 37 | } 38 | 39 | override fun setArtworkShapeKey(key: String) { 40 | preferences[Settings.ARTWORK_SHAPE_KEY] = key 41 | Firebase.analytics.logEvent(FirebaseAnalytics.Event.SELECT_ITEM) { 42 | param(FirebaseAnalytics.Param.ITEM_ID, key) 43 | } 44 | } 45 | 46 | override fun set(key: Key, value: O) { 47 | preferences[key] = value 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/impl/SettingsViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 14-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.impl 20 | 21 | import com.prime.media.settings.SettingsViewState 22 | import com.primex.preferences.Key 23 | 24 | class SettingsViewModel() : KoinViewModel(), SettingsViewState { 25 | override fun set(key: Key, value: O) { 26 | preferences[key] = value 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/local/DirectoryViewState.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media.local 2 | 3 | import androidx.compose.foundation.text.input.TextFieldState 4 | import com.prime.media.common.Filter 5 | import com.prime.media.common.Mapped 6 | import com.prime.media.common.menu.Action 7 | import kotlinx.coroutines.flow.StateFlow 8 | 9 | /** 10 | * Represents the common interface among directories 11 | * @property data 12 | */ 13 | interface DirectoryViewState { 14 | 15 | 16 | 17 | companion object 18 | 19 | val title: CharSequence 20 | 21 | val orders: List 22 | val data: StateFlow?> 23 | 24 | // filter. 25 | val query: TextFieldState 26 | val order: Filter 27 | 28 | // actions 29 | fun filter(ascending: Boolean = this.order.first, order: Action = this.order.second) 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/local/videos/VideosViewState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 16-11-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.local.videos 20 | 21 | import android.app.Activity 22 | import androidx.compose.foundation.text.input.TextFieldState 23 | import androidx.compose.material.icons.Icons 24 | import androidx.compose.material.icons.outlined.Delete 25 | import androidx.compose.material.icons.outlined.Edit 26 | import androidx.compose.material.icons.outlined.Share 27 | import com.prime.media.R 28 | import com.prime.media.common.Filter 29 | import com.prime.media.common.Mapped 30 | import com.prime.media.common.Route 31 | import com.prime.media.common.menu.Action 32 | import com.zs.core.store.Video 33 | import kotlinx.coroutines.flow.StateFlow 34 | 35 | object RouteVideos : Route 36 | 37 | /** 38 | * Represents the view state for the Videos screen. 39 | * 40 | * @property data The state flow containing mapped video data. 41 | * @property query The current text field state for filtering videos. 42 | * @property order The current filter applied to the video list. 43 | * @property actions The list of available actions that can be performed. 44 | * @property orders The list of possible orderings for the video list. 45 | */ 46 | interface VideosViewState { 47 | 48 | companion object { 49 | val ACTION_RENAME = Action(R.string.rename, Icons.Outlined.Edit) 50 | val ACTION_DELETE = Action(R.string.delete,Icons.Outlined.Delete) 51 | val ACTION_SHARE = Action(R.string.share, Icons.Outlined.Share) 52 | } 53 | 54 | val data: StateFlow?> 55 | val query: TextFieldState 56 | val order: Filter 57 | val actions: List 58 | val orders: List 59 | 60 | /** 61 | * Filters the video list based on the given ascending flag and order action. 62 | * 63 | * @param ascending Whether the videos should be ordered in ascending order. 64 | * @param order The action representing the order to apply. 65 | */ 66 | fun filter(ascending: Boolean = this.order.first, order: Action = this.order.second) 67 | 68 | /** 69 | * 70 | */ 71 | fun delete(activity: Activity, video: Video) 72 | 73 | /** 74 | * 75 | */ 76 | fun share(activity: Activity, file: Video) 77 | 78 | /** 79 | * Plays the specified video. 80 | * 81 | * @param video The video to be played. 82 | */ 83 | fun play(video: Video) 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/old/common/View.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media.old.common 2 | 3 | 4 | import android.annotation.SuppressLint 5 | import android.view.ViewGroup 6 | import android.widget.FrameLayout 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.toArgb 11 | import androidx.compose.ui.viewinterop.AndroidView 12 | import androidx.media3.common.Player 13 | import androidx.media3.ui.AspectRatioFrameLayout 14 | import androidx.media3.ui.PlayerView 15 | 16 | private const val TAG = "View" 17 | 18 | /** 19 | * A wrapper around Media3 [PlayerView] 20 | */ 21 | @SuppressLint("UnsafeOptInUsageError") 22 | @Composable 23 | fun PlayerView( 24 | player: Player?, 25 | modifier: Modifier = Modifier, 26 | resizeMode: Int = AspectRatioFrameLayout.RESIZE_MODE_FIT 27 | ) { 28 | AndroidView( 29 | modifier = modifier, 30 | factory = { 31 | PlayerView(it).apply { 32 | hideController() 33 | useController = false 34 | this.player = player 35 | this.resizeMode = resizeMode 36 | layoutParams = FrameLayout.LayoutParams( 37 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT 38 | ) 39 | clipToOutline = true 40 | // Set the Background Color of the player as Solid Black Color. 41 | setBackgroundColor(Color.Black.toArgb()) 42 | keepScreenOn = true 43 | } 44 | }, 45 | update = { it.resizeMode = resizeMode; it.player = player; it.keepScreenOn = true }, 46 | onRelease = {it.player = null; it.keepScreenOn = false} 47 | ) 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/old/common/util/PathUtils.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media.old.common.util 2 | 3 | import android.content.Context 4 | import org.jetbrains.annotations.Contract 5 | 6 | /** 7 | * A scope for [PathUtils] functions. 8 | */ 9 | object PathUtils { 10 | /** 11 | * The Unix separator character. 12 | */ 13 | const val PATH_SEPARATOR = '/' 14 | 15 | /** 16 | * The extension separator character. 17 | * @since 1.4 18 | */ 19 | const val EXTENSION_SEPARATOR = '.' 20 | 21 | const val HIDDEN_PATTERN = "/." 22 | 23 | /** 24 | * Gets the name minus the path from a full fileName. 25 | * 26 | * @param path the fileName to query 27 | * @return the name of the file without the path 28 | */ 29 | fun name(path: String): String = 30 | path.substring(path.lastIndexOf(PATH_SEPARATOR) + 1) 31 | 32 | /** 33 | * @return parent of path. 34 | */ 35 | fun parent(path: String): String = 36 | path.replace("$PATH_SEPARATOR${name(path = path)}", "") 37 | 38 | /** 39 | * Returns the file extension or null string if there is no extension. This method is a 40 | * convenience method for obtaining the extension of a url and has undefined 41 | * results for other Strings. 42 | * It is Assumed that Url is file 43 | * 44 | * @param url Url of the file 45 | * 46 | * @return extension 47 | */ 48 | fun extension(url: String): String? = 49 | if (url.contains(EXTENSION_SEPARATOR)) 50 | url.substring(url.lastIndexOf(EXTENSION_SEPARATOR) + 1).lowercase() 51 | else 52 | null 53 | 54 | /** 55 | * Checks if the file or its ancestors are hidden in System. 56 | */ 57 | @Contract(pure = true) 58 | fun areAncestorsHidden(path: String): Boolean = 59 | path.contains(HIDDEN_PATTERN) 60 | 61 | /** 62 | * Returns [bytes] as formatted data unit. 63 | */ 64 | @Deprecated(message = "find new solution.") 65 | fun toFormattedDataUnit( 66 | context: Context, 67 | bytes: Long, 68 | short: Boolean = true, 69 | ): String = when (short) { 70 | true -> android.text.format.Formatter.formatShortFileSize(context, bytes) 71 | else -> android.text.format.Formatter.formatFileSize(context, bytes) 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/old/editor/ViewState.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media.old.editor 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import androidx.compose.ui.graphics.ImageBitmap 6 | import androidx.compose.ui.text.input.TextFieldValue 7 | 8 | private const val TAG = "TagEditor" 9 | 10 | interface TagEditor { 11 | 12 | companion object { 13 | const val KEY_PRAM_DATA = "_data" 14 | const val route = "tag_editor/{${KEY_PRAM_DATA}}" 15 | fun direction(path: String) = 16 | "tag_editor/${Uri.encode(path)}" 17 | } 18 | 19 | val extraInfo: CharSequence? 20 | val artwork: ImageBitmap? 21 | 22 | 23 | var title: TextFieldValue 24 | var artist: TextFieldValue 25 | var album: TextFieldValue 26 | var composer: TextFieldValue 27 | var albumArtist: TextFieldValue 28 | var genre: TextFieldValue 29 | var year: TextFieldValue 30 | var trackNumber: TextFieldValue 31 | var diskNumber: TextFieldValue 32 | var totalDisks: TextFieldValue 33 | var lyrics: TextFieldValue 34 | var comment: TextFieldValue 35 | var copyright: TextFieldValue 36 | var url: TextFieldValue 37 | var originalArtist: TextFieldValue 38 | var publisher: TextFieldValue 39 | 40 | /** 41 | * Sets the artwork of the media item to the given URI, or removes the artwork if the URI is null. 42 | * This function modifies the APIC (Attached picture) frame in the ID3v2 tag, or deletes it if the URI is null. 43 | * @param new the URI of the new artwork image, or null to remove the artwork 44 | */ 45 | fun setArtwork(new: Uri?) 46 | 47 | /** 48 | * Saves the media item to the persistent memory, either replacing the original file or creating a new one depending on the user's strategy. 49 | * This function requires the context to be of ComponentActivity type, otherwise it will throw an exception. 50 | * @param ctx the context of the ComponentActivity that calls this function 51 | * @throws IllegalArgumentException if the context is not of ComponentActivity type 52 | */ 53 | fun save(ctx: Context) 54 | 55 | /** 56 | * Resets the tags of the media item to their original value, discarding any changes made by the user. 57 | * This function restores the ID3v2 tag to its initial state when the media item was created or loaded. 58 | */ 59 | fun reset() 60 | } 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/old/feedback/FeedbackViewState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 13-08-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.old.feedback 20 | 21 | import androidx.annotation.IntRange 22 | import androidx.compose.ui.text.input.TextFieldValue 23 | import androidx.navigation.NavController 24 | import com.prime.media.common.Route 25 | 26 | /** 27 | * A route for submitting feedback. 28 | */ 29 | object RouteFeedback : Route 30 | 31 | /** 32 | * Typealias for [FeedbackViewState.Companion] for easy access to its constants. 33 | */ 34 | typealias Feedback = FeedbackViewState.Companion 35 | 36 | /** 37 | * Represents the view state for the feedback screen. 38 | */ 39 | interface FeedbackViewState { 40 | companion object { 41 | /** 42 | * The maximum number of characters allowed in the feedback text.*/ 43 | const val TEXT_LIMIT_CHARS = 500 44 | 45 | /** 46 | * The default value for [rate] indicating that no rating has been set. 47 | */ 48 | const val RATING_NOT_SET = -1 49 | 50 | /** 51 | * The valid range for the rating value. 52 | */ 53 | val RATING_RANGE = 1..5 54 | 55 | /** 56 | * The maximum number of characters allowed in the feedback text. 57 | */ 58 | const val FEEDBACK_MAX_CHAR_COUNT = 500 59 | 60 | val FEEDBACK_TAG_OTHER = "tag_other" 61 | val FEEDBACK_TAG_BUG = "tag_bug_report" 62 | val FEEDBACK_TAG_SUGGESTION = "tag_suggestion" 63 | val FEEDBACK_TAG_FEEDBACK = "tag_feedback" 64 | val FEEDBACK_TAG_FEATURE_REQUEST = "tag_feature_request" 65 | } 66 | 67 | /** 68 | * The type or category of feedback this submission represents. 69 | * For example, this could be "Suggestion", "Bug Report","Question", etc. 70 | * Defaults to [FEEDBACK_TAG_FEEDBACK] if not explicitly set. 71 | */ 72 | var tag: String 73 | 74 | /** 75 | * A value between 1 and 5 that represents the rating given by the user. 76 | * A value of [RATING_NOT_SET] indicates that no rating has been set. 77 | */ 78 | @setparam:IntRange(from = 1, to = 5) 79 | var rate: Int 80 | 81 | /** 82 | * The feedback text entered by the user. 83 | */ 84 | var feedback: TextFieldValue 85 | 86 | /** 87 | * Sends the feedback to the server. 88 | */ 89 | fun submit(navController: NavController) 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/old/library/ViewState.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media.old.library 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.outlined.LibraryMusic 7 | import androidx.compose.ui.graphics.vector.ImageVector 8 | import com.prime.media.old.core.db.Audio 9 | import com.primex.core.Text 10 | import com.zs.core.db.Playlist 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.StateFlow 13 | 14 | interface Library { 15 | 16 | companion object { 17 | val route: String get() = "route_library" 18 | fun direction() = route 19 | } 20 | 21 | /** 22 | * The recently played tracks. 23 | */ 24 | val recent: StateFlow?> 25 | val carousel: StateFlow 26 | val newlyAdded: StateFlow?> 27 | 28 | /** 29 | * Callback method invoked upon clicking a history item. 30 | * 31 | * This method manages diverse scenarios contingent on the selected history item: 32 | * 33 | * - Scenario 1: In the event the newly added file is already present in the playback queue, 34 | * the queue will be directed to the chosen item's position, initiating playback from there. 35 | * 36 | * - Scenario 2: If the recently added item is absent from the queue, the ensuing sub-scenarios arise: 37 | * - Sub-Scenario 2.1: A Snackbar is exhibited to the user, offering options to either append 38 | * the item to the current queue at next or substitute the queue with this recently added playlist. 39 | * - Sub-Scenario 2.2: Further actions are contingent upon the user's decision. 40 | * 41 | * @param uri The URI of the history item clicked by the user. 42 | */ 43 | fun onClickRecentFile(uri: String) 44 | 45 | /** 46 | * Callback method triggered when a recently added item is clicked. 47 | * This function handles the action where the recently added file is either included in the 48 | * playback queue or prompts the user to replace the existing queue with recently added items. 49 | * 50 | * @param id The unique identifier of the recently added history item. 51 | */ 52 | fun onClickRecentAddedFile(id: Long) 53 | } -------------------------------------------------------------------------------- /app/src/main/java/com/prime/media/personalize/PersonalizeViewState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 17-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.media.personalize 20 | 21 | import androidx.compose.ui.graphics.Shape 22 | import com.prime.media.common.Route 23 | import com.primex.preferences.Key 24 | 25 | object RoutePersonalize: Route 26 | 27 | 28 | interface PersonalizeViewState { 29 | /** 30 | * Sets the current widget to [id] 31 | */ 32 | fun setInAppWidget(id: String) 33 | 34 | fun setArtworkShapeKey(key: String) 35 | 36 | /** 37 | * Sets the [key] to [value] 38 | */ 39 | operator fun set(key: Key, value: O) 40 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 15 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/default_art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/drawable/default_art.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_artist.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_branded_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/drawable/ic_branded_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_remove_ads.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/drawable/ic_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/media3_notification_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/media3_notification_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/raw/bg_blur_color_blob.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/bg_blur_color_blob.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/bg_dark_wavy_lines.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/bg_dark_wavy_lines.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/bg_fading_streight_lines.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/bg_fading_streight_lines.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/bg_gradeint_dots.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/bg_gradeint_dots.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/bg_rotating_color_gradient.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/bg_rotating_color_gradient.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/loading_hand.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/loading_hand.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_bg_baloon_in_air.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_bg_baloon_in_air.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_bg_blur.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_bg_blur.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_bg_fluid_color.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_bg_fluid_color.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_bg_snowflakes.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_bg_snowflakes.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_golden_ticket.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_golden_ticket.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_green_list_loading.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_green_list_loading.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_list_loading_grey.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_list_loading_grey.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/lt_list_loading_shimmer_2.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/lt_list_loading_shimmer_2.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/play_pause_circle_bordered.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/play_pause_circle_bordered.lottie -------------------------------------------------------------------------------- /app/src/main/res/raw/play_pause_filled.lottie: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/app/src/main/res/raw/play_pause_filled.lottie -------------------------------------------------------------------------------- /app/src/main/res/values-ar-rSA/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-ca-rES/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-cs-rCZ/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-da-rDK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-de-rDE/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-el-rGR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-fi-rFI/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr-rFR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-hi-rIN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-hu-rHU/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-it-rIT/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-iw-rIL/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-ja-rJP/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-ko-rKR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-nl-rNL/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-no-rNO/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values-pt-rPT/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #000000 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/theme.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/what_s_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | What\'s New (v3.8.0) 5 | 6 | \n - Made subtle yet meaningful UI improvements. 7 | \n - Introduced a user-friendly swipe gesture to seamlessly adjust volume & brightness, offering an intuitive and efficient control experience. 8 | \n - Positioned the controller lock feature at a more accessible and convenient location 9 | \n - Introducing a variety of stunning Album Art styles! You can now customize your experience by selecting your favorite style from the Personalize Screen. 10 | \n - Say goodbye to the conventional white/dark themes of the Console! Audiofy now features the all-new Ambient Mode, showcasing an exquisite blend of intricate blurs and vibrant color combinations for a truly captivating visual experience. 11 | \n - Introduced multiple options for selecting the Accent Color, including manual selection and wallpaper-based choices. 12 | 13 | 14 | 15 | @string/what_s_new_toast 16 | 17 | 18 | 19 | 20 | @string/what_s_new_latest 21 | v3.3.0 22 | 23 | \n We\'ve enhanced the functionality of the app. 24 | \n We\'re utilizing the new, advanced NowPlaying API for real-time updates regarding playback metadata state. 25 | \n We\'ve added a new Dynamic Widget that automatically adjusts to size. 26 | \n More App Widget styles are coming soon. 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/test/java/com/prime/media/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.prime.media 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.android.application) apply false 4 | alias(libs.plugins.jetbrains.kotlin.android) apply false 5 | alias(libs.plugins.compose.compiler) apply false 6 | alias(libs.plugins.android.library) apply false 7 | alias(libs.plugins.google.service) apply false 8 | alias(libs.plugins.crashanlytics) apply false 9 | alias(libs.plugins.android.dynamic.feature) apply false 10 | alias(libs.plugins.ksp) apply false 11 | } -------------------------------------------------------------------------------- /codex/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /codex/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.dynamic.feature) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | } 5 | android { 6 | namespace = "com.prime.codex" 7 | compileSdk = 34 8 | defaultConfig { 9 | minSdk = 21 10 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 11 | } 12 | buildTypes { 13 | release { 14 | isMinifyEnabled = false 15 | proguardFiles("proguard-rules.pro") 16 | } 17 | } 18 | compileOptions { 19 | sourceCompatibility = JavaVersion.VERSION_1_8 20 | targetCompatibility = JavaVersion.VERSION_1_8 21 | } 22 | kotlinOptions { 23 | jvmTarget = "1.8" 24 | freeCompilerArgs = listOf( 25 | "-Xopt-in=kotlin.RequiresOptIn", 26 | "-Xcontext-receivers" 27 | ) 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation(project(":app")) 33 | implementation(libs.nextlib) // To add media3 software decoders and extensions 34 | } -------------------------------------------------------------------------------- /codex/src/androidTest/java/com/prime/codex/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.prime.codex 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.prime.codex", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /codex/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /codex/src/main/java/com/prime/codex/Codex.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 23-07-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.prime.codex 20 | 21 | import android.annotation.SuppressLint 22 | import android.content.Context 23 | import io.github.anilbeesetti.nextlib.media3ext.ffdecoder.NextRenderersFactory 24 | 25 | // com.prime.codex.CodexKt.Codex 26 | @SuppressLint("UnsafeOptInUsageError") 27 | fun Codex(context: Context) = NextRenderersFactory(context) -------------------------------------------------------------------------------- /codex/src/test/java/com/prime/codex/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.prime.codex 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core-ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core-ui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.compose.compiler) 5 | } 6 | 7 | android { 8 | namespace = "com.zs.core_ui" 9 | compileSdk = 34 10 | defaultConfig { 11 | minSdk = 21 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | buildTypes { 16 | release { 17 | isMinifyEnabled = false 18 | proguardFiles( 19 | getDefaultProguardFile("proguard-android-optimize.txt"), 20 | "proguard-rules.pro" 21 | ) 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility = JavaVersion.VERSION_1_8 26 | targetCompatibility = JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = "1.8" 30 | freeCompilerArgs = listOf( 31 | "-Xopt-in=kotlin.RequiresOptIn", 32 | "-Xopt-in=com.primex.core.ExperimentalToolkitApi" 33 | ) 34 | } 35 | buildFeatures { compose = true } 36 | } 37 | 38 | dependencies { 39 | api(libs.bundles.compose) 40 | api(libs.bundles.material.icons) 41 | api(libs.coil.compose) 42 | debugApi(libs.bundles.compose.preview) 43 | 44 | implementation(libs.androidx.core.ktx) 45 | implementation(libs.androidx.window) 46 | implementation(libs.androidx.palette.ktx) 47 | implementation(libs.androidx.graphics.shapes) 48 | // TODO - Revert these to impl once old code is removed from the project. 49 | api(libs.wavy.slider) 50 | api(libs.lottie.compose) 51 | 52 | } 53 | 54 | // TODO: It appears that Material3 components may be leaking into this project, which is intended to support Material2. 55 | // Please investigate if this issue is related to the Wavy Slider and resolve it. Once the main issue is fixed, 56 | // consider removing this block of code. 57 | configurations { 58 | all { exclude(group = "androidx.compose.material3", module = "material3") } 59 | } -------------------------------------------------------------------------------- /core-ui/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/core-ui/consumer-rules.pro -------------------------------------------------------------------------------- /core-ui/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 -------------------------------------------------------------------------------- /core-ui/src/androidTest/java/com/zs/core_ui/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core_ui 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.zs.core_ui.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core-ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/LazyList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 18-11-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui 20 | 21 | import androidx.compose.foundation.ExperimentalFoundationApi 22 | import androidx.compose.foundation.lazy.grid.LazyGridItemScope 23 | import androidx.compose.foundation.lazy.grid.LazyGridScope 24 | import androidx.compose.foundation.lazy.grid.LazyGridState 25 | import androidx.compose.runtime.Composable 26 | import androidx.compose.ui.layout.Layout 27 | import androidx.compose.ui.layout.positionInParent 28 | import androidx.compose.ui.unit.constrainHeight 29 | import androidx.compose.ui.unit.constrainWidth 30 | 31 | private const val TAG = "LazyList" 32 | 33 | /** 34 | * A sticky header implementation that respects the top padding of the content. 35 | * This should be removed when an official solution is provided. 36 | * Currently, the only issue is that the sticky layout and the next item overlap before moving, 37 | * while the sticky header should start moving when the next item is about to become sticky. 38 | * 39 | * @param state The state of the LazyGrid. 40 | * @param key The key for the sticky header item. 41 | * @param contentType The type of content for the sticky header. 42 | * @param content The composable content for the sticky header. 43 | */ 44 | @OptIn(ExperimentalFoundationApi::class) 45 | fun LazyGridScope.stickyHeader( 46 | state: LazyGridState, 47 | key: Any? = null, 48 | contentType: Any? = null, 49 | content: @Composable LazyGridItemScope.() -> Unit 50 | ) { 51 | stickyHeader( 52 | key = key, 53 | contentType = contentType, 54 | content = { 55 | Layout(content = { content() }) { measurables, constraints -> 56 | val placeable = measurables[0].measure(constraints) 57 | val width = constraints.constrainWidth(placeable.width) 58 | val height = constraints.constrainHeight(placeable.height) 59 | layout(width, height) { 60 | val posY = coordinates?.positionInParent()?.y?.toInt() ?: 0 61 | val paddingTop = state.layoutInfo.beforeContentPadding 62 | var top = (paddingTop - posY).coerceIn(0, paddingTop) 63 | placeable.placeRelativeWithLayer(0, top) 64 | } 65 | } 66 | } 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/NightMode.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core_ui 2 | 3 | enum class NightMode { 4 | /** 5 | * Night mode which uses always uses a light mode, enabling {@code notnight} qualified 6 | * resources regardless of the time. 7 | * 8 | * @see #setLocalNightMode(int) 9 | */ 10 | YES, 11 | 12 | /** 13 | * Night mode which uses always uses a dark mode, enabling {@code night} qualified 14 | * resources regardless of the time. 15 | * 16 | * @see #setLocalNightMode(int) 17 | */ 18 | NO, 19 | 20 | /** 21 | * Mode which uses the system's night mode setting to determine if it is night or not. 22 | * 23 | * @see #setLocalNightMode(int) 24 | */ 25 | FOLLOW_SYSTEM 26 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/ScaleIndication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 10-08-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui 20 | 21 | import androidx.compose.animation.core.Animatable 22 | import androidx.compose.animation.core.spring 23 | import androidx.compose.foundation.IndicationNodeFactory 24 | import androidx.compose.foundation.interaction.InteractionSource 25 | import androidx.compose.foundation.interaction.PressInteraction 26 | import androidx.compose.runtime.Stable 27 | import androidx.compose.ui.Modifier 28 | import androidx.compose.ui.geometry.Offset 29 | import androidx.compose.ui.graphics.drawscope.ContentDrawScope 30 | import androidx.compose.ui.graphics.drawscope.scale 31 | import androidx.compose.ui.node.DelegatableNode 32 | import androidx.compose.ui.node.DrawModifierNode 33 | import kotlinx.coroutines.flow.collectLatest 34 | import kotlinx.coroutines.launch 35 | 36 | private const val TAG = "ScaleIndication" 37 | 38 | private class ScaleIndicationNode( 39 | private val interactionSource: InteractionSource 40 | ) : Modifier.Node(), DrawModifierNode { 41 | var currentPressPosition: Offset = Offset.Zero 42 | val animatedScalePercent = Animatable(1f) 43 | 44 | private suspend fun animateToPressed(pressPosition: Offset) { 45 | currentPressPosition = pressPosition 46 | animatedScalePercent.animateTo(0.9f, spring()) 47 | } 48 | 49 | private suspend fun animateToResting() { 50 | animatedScalePercent.animateTo(1f, spring()) 51 | } 52 | 53 | override fun onAttach() { 54 | coroutineScope.launch { 55 | interactionSource.interactions.collectLatest { interaction -> 56 | when (interaction) { 57 | is PressInteraction.Press -> animateToPressed(interaction.pressPosition) 58 | is PressInteraction.Release -> animateToResting() 59 | is PressInteraction.Cancel -> animateToResting() 60 | } 61 | } 62 | } 63 | } 64 | 65 | override fun ContentDrawScope.draw() { 66 | scale( 67 | scale = animatedScalePercent.value, 68 | pivot = currentPressPosition 69 | ) { 70 | this@draw.drawContent() 71 | } 72 | } 73 | } 74 | 75 | 76 | @Stable 77 | fun scale( 78 | ): IndicationNodeFactory { 79 | return ScaleIndicationNodeFactory 80 | } 81 | 82 | private object ScaleIndicationNodeFactory : IndicationNodeFactory { 83 | override fun create(interactionSource: InteractionSource): DelegatableNode { 84 | return ScaleIndicationNode(interactionSource) 85 | } 86 | 87 | override fun hashCode(): Int = -1 88 | 89 | override fun equals(other: Any?) = other === this 90 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/WallpaperAccentColor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 12-08-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui 20 | 21 | import android.app.WallpaperColors 22 | import android.graphics.Bitmap 23 | import android.os.Build 24 | import androidx.annotation.ColorInt 25 | import androidx.compose.runtime.Stable 26 | import androidx.compose.ui.graphics.Color 27 | import androidx.compose.ui.graphics.toArgb 28 | import androidx.palette.graphics.Palette 29 | 30 | /** 31 | * Extracts a color from a bitmap to be used as a wallpaper accent color. 32 | * 33 | * On Android O MR1 and above, it uses [WallpaperColors] to extract the primary color. 34 | * On older versions, it uses [Palette] to extract a vibrant color based on the provided theme. 35 | * If no suitable color is found in the palette, the provided default color is returned. 36 | * 37 | * @param bitmap The bitmap to extract the color from. Must not be null or empty. 38 | * @param isDark Whether the current theme is dark. 39 | * @param default The default color to use if no suitable color is found. 40 | * 41 | * @return The extracted color as an ARGB integer. 42 | */ 43 | @Stable 44 | @ColorInt 45 | fun WallpaperAccentColor( 46 | bitmap: Bitmap?, 47 | isDark: Boolean, 48 | default: Color 49 | ): Int { 50 | if (bitmap == null || bitmap.isRecycled || bitmap.width == 0 || bitmap.height == 0) { 51 | return default.toArgb() 52 | } 53 | return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { 54 | WallpaperColors.fromBitmap(bitmap).primaryColor.toArgb() 55 | } else { 56 | // Generate a color palette from the bitmap 57 | val palette = Palette.from(bitmap).generate() 58 | val argb = default.toArgb() // Obtain the ARGB value of the default color for potential use 59 | // Pick a vibrant color based on the current theme, or use the default if none is found 60 | if (isDark) palette.getLightVibrantColor(argb) else palette.getDarkVibrantColor(argb) 61 | } 62 | } 63 | 64 | 65 | -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/coil/RsBlurTransformation.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package com.zs.core_ui.coil 4 | 5 | import android.content.Context 6 | import android.graphics.Bitmap 7 | import android.graphics.Paint 8 | import android.renderscript.Allocation 9 | import android.renderscript.Element 10 | import android.renderscript.RenderScript 11 | import android.renderscript.ScriptIntrinsicBlur 12 | import androidx.annotation.RequiresApi 13 | import androidx.core.graphics.applyCanvas 14 | import androidx.core.graphics.createBitmap 15 | import coil.size.Size 16 | import coil.transform.Transformation 17 | 18 | private const val TAG = "RsBlurTransformation" 19 | 20 | private const val DEFAULT_RADIUS = 10f 21 | private const val DEFAULT_SAMPLING = 1f 22 | 23 | private val Bitmap.safeConfig: Bitmap.Config 24 | get() = config ?: Bitmap.Config.ARGB_8888 25 | 26 | /** 27 | * A [Transformation] that applies a Gaussian blur to an image. 28 | * 29 | * @param context The [Context] used to create a [RenderScript] instance. 30 | * @param radius The radius of the blur. 31 | * @param sampling The sampling multiplier used to scale the image. Values > 1 32 | * will downscale the image. Values between 0 and 1 will upscale the image. 33 | */ 34 | @RequiresApi(18) 35 | class RsBlurTransformation @JvmOverloads constructor( 36 | private val context: Context, 37 | private val radius: Float = DEFAULT_RADIUS, 38 | private val sampling: Float = DEFAULT_SAMPLING 39 | ) : Transformation { 40 | 41 | init { 42 | require(radius in 0.0..25.0) { "radius must be in [0, 25]." } 43 | require(sampling > 0) { "sampling must be > 0." } 44 | } 45 | 46 | @Suppress("NullableToStringCall") 47 | override val cacheKey = "${RsBlurTransformation::class.java.name}-$radius-$sampling" 48 | 49 | override suspend fun transform(input: Bitmap, size: Size): Bitmap { 50 | val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG) 51 | 52 | val scaledWidth = (input.width / sampling).toInt() 53 | val scaledHeight = (input.height / sampling).toInt() 54 | val output = createBitmap(scaledWidth, scaledHeight, input.safeConfig) 55 | output.applyCanvas { 56 | scale(1 / sampling, 1 / sampling) 57 | drawBitmap(input, 0f, 0f, paint) 58 | } 59 | 60 | var script: RenderScript? = null 61 | var tmpInt: Allocation? = null 62 | var tmpOut: Allocation? = null 63 | var blur: ScriptIntrinsicBlur? = null 64 | try { 65 | script = RenderScript.create(context) 66 | tmpInt = Allocation.createFromBitmap( 67 | script, 68 | output, 69 | Allocation.MipmapControl.MIPMAP_NONE, 70 | Allocation.USAGE_SCRIPT 71 | ) 72 | tmpOut = Allocation.createTyped(script, tmpInt.type) 73 | blur = ScriptIntrinsicBlur.create(script, Element.U8_4(script)) 74 | blur.setRadius(radius) 75 | blur.setInput(tmpInt) 76 | blur.forEach(tmpOut) 77 | tmpOut.copyTo(output) 78 | } finally { 79 | script?.destroy() 80 | tmpInt?.destroy() 81 | tmpOut?.destroy() 82 | blur?.destroy() 83 | } 84 | 85 | return output 86 | } 87 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/CompactDisk.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core_ui.shape 2 | 3 | import androidx.compose.ui.geometry.Offset 4 | import androidx.compose.ui.geometry.Rect 5 | import androidx.compose.ui.geometry.Size 6 | import androidx.compose.ui.graphics.Outline 7 | import androidx.compose.ui.graphics.Path 8 | import androidx.compose.ui.graphics.PathOperation 9 | import androidx.compose.ui.graphics.Shape 10 | import androidx.compose.ui.unit.Density 11 | import androidx.compose.ui.unit.LayoutDirection 12 | 13 | /** 14 | * The shape of a compact disk. 15 | */ 16 | val CompactDisk = 17 | object : Shape { 18 | override fun createOutline( 19 | size: Size, 20 | layoutDirection: LayoutDirection, 21 | density: Density 22 | ): Outline { 23 | val innerRadiusFraction = 0.259f 24 | val radius = kotlin.math.min(size.width, size.height) / 2f // the outer radius 25 | val innerRadius = radius * innerRadiusFraction // the inner radius 26 | val center = Offset(size.width / 2f, size.height / 2f) // the center of the shape 27 | 28 | // create a path for the outer circle 29 | val outerPath = Path().apply { 30 | addArc( 31 | Rect( 32 | left = center.x - radius, 33 | top = center.y - radius, 34 | right = center.x + radius, 35 | bottom = center.y + radius, 36 | ), 37 | startAngleDegrees = 0f, 38 | sweepAngleDegrees = 360f 39 | ) 40 | } 41 | 42 | // create a path for the inner circle 43 | val innerPath = Path().apply { 44 | addArc( 45 | Rect( 46 | left = center.x - innerRadius, 47 | top = center.y - innerRadius, 48 | right = center.x + innerRadius, 49 | bottom = center.y + innerRadius, 50 | ), 51 | startAngleDegrees = 0f, 52 | sweepAngleDegrees = 360f 53 | ) 54 | } 55 | 56 | // subtract the inner path from the outer path using PathOperation.Difference 57 | val resultPath = Path().apply { 58 | op(outerPath, innerPath, PathOperation.Difference) 59 | } 60 | 61 | // return an outline based on the result path 62 | return Outline.Generic(resultPath) 63 | } 64 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/EndConcaveShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 11-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui.shape 20 | 21 | import androidx.compose.ui.geometry.Size 22 | import androidx.compose.ui.graphics.Outline 23 | import androidx.compose.ui.graphics.Path 24 | import androidx.compose.ui.graphics.Shape 25 | import androidx.compose.ui.unit.Density 26 | import androidx.compose.ui.unit.Dp 27 | import androidx.compose.ui.unit.LayoutDirection 28 | 29 | private const val TAG = "EndConcaveShape" 30 | 31 | /** 32 | * A shape that represents a rectangle with a concave top. 33 | * 34 | * @param radius the radius of the concave curve at the top of the shape. 35 | */ 36 | class EndConcaveShape(private val radius: Dp) : Shape { 37 | override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline { 38 | val (w, h) = size 39 | val radiusPx = with(density) { radius.toPx() } 40 | 41 | val path = Path().apply { 42 | moveTo(w, 0f) 43 | quadraticTo(w -radiusPx, 0f, w - radiusPx, radiusPx) 44 | lineTo(w-radiusPx, h- radiusPx) 45 | quadraticTo(w - radiusPx, h, w, h) 46 | lineTo(0f, h) 47 | lineTo(0f, 0f) 48 | lineTo(w, 0f) 49 | } 50 | return Outline.Generic(path) 51 | } 52 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/Folder.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core_ui.shape 2 | 3 | import androidx.compose.foundation.shape.GenericShape 4 | 5 | private const val TAG = "Folder" 6 | 7 | /** 8 | * Defines a [GenericShape] simpler to [Icons.Rounded.Folder] 9 | */ 10 | val FolderShape = GenericShape { (x, y), _ -> 11 | val radius = 0.18f * x 12 | val stepAt = 0.40f * x 13 | moveTo(radius, 0f) 14 | lineTo(stepAt, 0f) 15 | lineTo(stepAt + radius, radius) 16 | lineTo(x - radius, radius) 17 | quadraticTo(x, radius, x, 2 * radius) 18 | lineTo(x, y - radius) 19 | quadraticTo(x, y, x - radius, y) 20 | lineTo(radius, y) 21 | quadraticTo(0f, y, 0f, y - radius) 22 | lineTo(0f, radius) 23 | quadraticTo(0f, 0f, radius, 0f) 24 | close() 25 | } 26 | -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/HeartShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 sheik 3 | * 4 | * Created by sheik on 03-03-2025. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui.shape 20 | 21 | import androidx.compose.foundation.shape.GenericShape 22 | 23 | /** 24 | * Represents the shape of the [Heart](https://user-images.githubusercontent.com/6584143/125109896-8e16ae00-e101-11eb-99ee-4d4a7696c667.png) 25 | */ 26 | val HeartShape = GenericShape { size, _ -> 27 | val h = size.height 28 | val w = size.width 29 | lineTo(0.5f*w, 0.25f*h) 30 | cubicTo(0.5f*w, 0.225f*h, 0.458333333333333f*w, 0.125f*h, 0.291666666666667f*w, 0.125f*h) 31 | cubicTo(0.0416666666666667f*w, 0.125f*h, 0.0416666666666667f*w, 0.4f*h, 0.0416666666666667f*w, 0.4f*h) 32 | cubicTo(0.0416666666666667f*w, 0.583333333333333f*h, 0.208333333333333f*w, 0.766666666666667f*h, 0.5f*w, 0.916666666666667f*h) 33 | cubicTo(0.791666666666667f*w, 0.766666666666667f*h, 0.958333333333333f*w, 0.583333333333333f*h, 0.958333333333333f*w, 0.4f*h) 34 | cubicTo(0.958333333333333f*w, 0.4f*h, 0.958333333333333f*w, 0.125f*h, 0.708333333333333f*w, 0.125f*h) 35 | cubicTo(0.583333333333333f*w, 0.125f*h, 0.5f*w, 0.225f*h, 0.5f*w, 0.25f*h) 36 | close() 37 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/RoundedPolygonShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 20-09-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui.shape 20 | 21 | import androidx.annotation.FloatRange 22 | import androidx.annotation.IntRange 23 | import androidx.compose.ui.geometry.Rect 24 | import androidx.compose.ui.geometry.Size 25 | import androidx.compose.ui.graphics.Matrix 26 | import androidx.compose.ui.graphics.Outline 27 | import androidx.compose.ui.graphics.Path 28 | import androidx.compose.ui.graphics.Shape 29 | import androidx.compose.ui.graphics.asComposePath 30 | import androidx.compose.ui.unit.Density 31 | import androidx.compose.ui.unit.LayoutDirection 32 | import androidx.graphics.shapes.CornerRounding 33 | import androidx.graphics.shapes.RoundedPolygon 34 | import androidx.graphics.shapes.toPath 35 | import kotlin.math.max 36 | 37 | private fun RoundedPolygon.getBounds() = calculateBounds().let { Rect(it[0], it[1], it[2], it[3]) } 38 | 39 | /** 40 | * A shape representing a rounded polygon. 41 | * 42 | * This shape creates a polygon with the specified number of sides and applies rounding to its corners. 43 | * The rounding is controlled by the `rounding` parameter, which represents the fraction of the side length used for rounding. 44 | * 45 | * @param sides The number of sides of the polygon. Must be 3 or greater. 46 | * @param rounding The rounding factor, as a fraction of the side length. Should be a value between 0.0 (no rounding) and 1.0 (maximum rounding). 47 | */ 48 | class RoundedPolygonShape( 49 | @IntRange(3) private val sides: Int, 50 | @FloatRange(0.0, 1.0) private val rounding: Float = 0.0f 51 | ): Shape { 52 | 53 | private val polygon = RoundedPolygon(sides, rounding = CornerRounding(rounding, 1f)) 54 | private var path = Path() 55 | private var matrix = Matrix() 56 | 57 | override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline { 58 | path.rewind() 59 | path = polygon.toPath().asComposePath() 60 | matrix.reset() 61 | val bounds = polygon.getBounds() 62 | val maxDimension = max(bounds.width, bounds.height) 63 | matrix.scale(size.width / maxDimension, size.height / maxDimension) 64 | matrix.translate(-bounds.left, -bounds.top) 65 | 66 | path.transform(matrix) 67 | return Outline.Generic(path) 68 | } 69 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/RoundedStarShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 26-02-2025. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui.shape 20 | 21 | import androidx.compose.ui.geometry.Size 22 | import androidx.compose.ui.graphics.Outline 23 | import androidx.compose.ui.graphics.Path 24 | import androidx.compose.ui.graphics.Shape 25 | import androidx.compose.ui.unit.Density 26 | import androidx.compose.ui.unit.LayoutDirection 27 | import kotlin.math.PI 28 | import kotlin.math.cos 29 | import kotlin.math.min 30 | import kotlin.math.sin 31 | 32 | /** 33 | * Shape describing star with rounded corners 34 | * 35 | * Note: The shape draws within the minimum of provided width and height so can't be used to create stretched shape. 36 | * 37 | * @param sides number of sides. 38 | * @param curve a double value between 0.0 - 1.0 for modifying star curve. 39 | * @param rotation value between 0 - 360 40 | * @param iterations a value between 0 - 360 that determines the quality of star shape. 41 | */ 42 | class RoundedStarShape( 43 | private val sides: Int, 44 | private val curve: Double = 0.09, 45 | private val rotation: Float = 0f, 46 | iterations: Int = 360, 47 | ) : Shape { 48 | 49 | private companion object { 50 | const val TWO_PI = 2 * PI 51 | } 52 | 53 | private val steps = (TWO_PI) / min(iterations, 360) 54 | private val rotationDegree = (PI / 180) * rotation 55 | 56 | override fun createOutline( 57 | size: Size, 58 | layoutDirection: LayoutDirection, 59 | density: Density 60 | ): Outline = Outline.Generic(Path().apply { 61 | 62 | 63 | val r = min(size.height, size.width) * 0.4 * mapRange(1.0, 0.0, 0.5, 1.0, curve) 64 | 65 | val xCenter = size.width * .5f 66 | val yCenter = size.height * .5f 67 | 68 | moveTo(xCenter, yCenter) 69 | 70 | var t = 0.0 71 | 72 | while (t <= TWO_PI) { 73 | val x = r * (cos(t - rotationDegree) * (1 + curve * cos(sides * t))) 74 | val y = r * (sin(t - rotationDegree) * (1 + curve * cos(sides * t))) 75 | lineTo((x + xCenter).toFloat(), (y + yCenter).toFloat()) 76 | 77 | t += steps 78 | } 79 | 80 | val x = r * (cos(t - rotationDegree) * (1 + curve * cos(sides * t))) 81 | val y = r * (sin(t - rotationDegree) * (1 + curve * cos(sides * t))) 82 | lineTo((x + xCenter).toFloat(), (y + yCenter).toFloat()) 83 | 84 | }) 85 | 86 | 87 | private fun mapRange(a: Double, b: Double, c: Double, d: Double, x: Double): Double { 88 | return (x - a) / (b - a) * (d - c) + c 89 | } 90 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/TagShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 03-03-2025. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui.shape 20 | 21 | import androidx.compose.foundation.shape.GenericShape 22 | 23 | // TODO: TagShape from a val to a class accepting cornerRadius and maybe use layoutDirection for RTL support. 24 | 25 | /** 26 | * Represents the shape of the [Tag](https://user-images.githubusercontent.com/6584143/125108986-63782580-e100-11eb-9438-19e5594f4fea.png) 27 | * 28 | */ 29 | val TagShape = GenericShape { size, _ -> 30 | val w = size.width / 27f 31 | val h = size.height / 11f 32 | 33 | moveTo(5f * w, 0f * h) 34 | lineTo(25f * w, 0f * h) 35 | cubicTo(26f * w, 0f, 27f * w, 1f * h, 27f * w, 2f * h) 36 | lineTo(27f * w, 9f * h) 37 | cubicTo(27f * w, 10f * h, 26f * w, 11f * h, 25f * w, 11f * h) 38 | lineTo(5f * w, 11f * h) 39 | lineTo(1f * w, 7f * h) 40 | cubicTo(0f * w, 6f * h, 0f * w, 5f * h, 1f * w, 4f * h) 41 | } -------------------------------------------------------------------------------- /core-ui/src/main/java/com/zs/core_ui/shape/TopConcaveShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 11-10-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core_ui.shape 20 | 21 | import androidx.compose.ui.geometry.Size 22 | import androidx.compose.ui.graphics.Outline 23 | import androidx.compose.ui.graphics.Path 24 | import androidx.compose.ui.graphics.Shape 25 | import androidx.compose.ui.unit.Density 26 | import androidx.compose.ui.unit.Dp 27 | import androidx.compose.ui.unit.LayoutDirection as ld 28 | 29 | /** 30 | * A shape that represents a rectangle with a concave top. 31 | * 32 | * @param radius the radius of the concave curve at the top of the shape. 33 | */ 34 | class TopConcaveShape(private val radius: Dp) : Shape { 35 | override fun createOutline(size: Size, layoutDirection: ld, density: Density): Outline { 36 | val (w, h) = size 37 | val radiusPx = with(density) { radius.toPx() } 38 | 39 | val path = Path().apply { 40 | // Start from top left corner with a radius 41 | moveTo(radiusPx, radiusPx) 42 | quadraticTo(0f, radiusPx, 0f, 0f) 43 | 44 | // Draw left side 45 | lineTo(0f, h) 46 | lineTo(w, h) 47 | lineTo(w, 0f) 48 | 49 | // Draw top right corner with a radius 50 | quadraticTo(w, radiusPx, w - radiusPx, radiusPx) 51 | 52 | // Close the path 53 | lineTo(radiusPx, radiusPx) 54 | } 55 | return Outline.Generic(path) 56 | } 57 | } -------------------------------------------------------------------------------- /core-ui/src/test/java/com/zs/core_ui/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core_ui 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.ksp) 5 | } 6 | 7 | android { 8 | namespace = "com.zs.core" 9 | compileSdk = 34 10 | defaultConfig { 11 | minSdk = 21 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles("consumer-rules.pro") 14 | } 15 | buildTypes { 16 | release { 17 | isMinifyEnabled = false 18 | proguardFiles( 19 | getDefaultProguardFile("proguard-android-optimize.txt"), 20 | "proguard-rules.pro" 21 | ) 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility = JavaVersion.VERSION_1_8 26 | targetCompatibility = JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { jvmTarget = "1.8" } 29 | } 30 | 31 | dependencies { 32 | implementation(libs.androidx.core.ktx) 33 | implementation(libs.bundles.room) 34 | implementation(libs.google.billing.ktx) 35 | 36 | ksp(libs.room.compiler) 37 | api(libs.bundles.media3) 38 | api(libs.mp3agic) 39 | } -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/core/consumer-rules.pro -------------------------------------------------------------------------------- /core/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 -------------------------------------------------------------------------------- /core/src/androidTest/java/com/zs/core/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.zs.core.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/zs/core/Temp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by 2024 on 26-09-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core 20 | 21 | -------------------------------------------------------------------------------- /core/src/main/java/com/zs/core/paymaster/Purchase.kt: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright 2024 Zakir Sheikh 4 | * 5 | * Created by 2024 on 02-10-2024. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * https://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.zs.core.paymaster 21 | 22 | 23 | import com.android.billingclient.api.ProductDetails 24 | import com.android.billingclient.api.Purchase as GP_Purchase 25 | 26 | /** 27 | * Representing a purchase. 28 | * 29 | * This class wraps a [GP_Purchase] purchase object and provides convenient access to its properties. 30 | * 31 | * @property value The underlying [GP_Purchase] purchase object. 32 | * @property isAcknowledged Whether the purchase has been acknowledged. 33 | * @property state The state of the purchase. [com.android.billingclient.api.Purchase.PurchaseState] 34 | * @property quantity The quantity of the purchased items. 35 | * @property id The ID of the first product in the purchase, as assigned in Play Console Billing. 36 | * @property time The time of the purchase. 37 | */ 38 | @JvmInline 39 | value class Purchase internal constructor(internal val value: GP_Purchase) { 40 | val isAcknowledged get() = value.isAcknowledged 41 | val state get() = value.purchaseState 42 | val quantity get() = value.quantity 43 | val id get() = value.products.first() 44 | val time get() = value.purchaseTime 45 | 46 | constructor(JSONInfo: String, signature: String): this(GP_Purchase(JSONInfo, signature)) 47 | } 48 | 49 | /** 50 | * @return `true` if this [Purchase] object is `non-null`, has been `acknowledged`, and is in the 51 | * [GP_Purchase.PurchaseState.PURCHASED] state. Returns `false` otherwise. 52 | */ 53 | val Purchase?.purchased get() = if (this == null) false else isAcknowledged && state == GP_Purchase.PurchaseState.PURCHASED 54 | 55 | /** 56 | * Represents a product. 57 | * 58 | * @property value The underlying [ProductDetails] object. 59 | * @property title The title of the product. 60 | * @property description The description of the product. 61 | * @property formattedPrice The formatted price of the product, or null if not available. 62 | * @property id The ID of the product. 63 | */ 64 | @JvmInline 65 | value class ProductInfo internal constructor(internal val value: ProductDetails) { 66 | val title get() = value.title.trim() 67 | val description get() = value.description.trim() 68 | val formattedPrice get() = value.oneTimePurchaseOfferDetails?.formattedPrice?.trim() 69 | val id get() = value.productId 70 | } 71 | 72 | -------------------------------------------------------------------------------- /core/src/main/java/com/zs/core/playback/PlaybackControllerImpl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 17-11-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core.playback 20 | 21 | import android.content.ComponentName 22 | import android.content.Context 23 | import androidx.media3.session.MediaBrowser 24 | import androidx.media3.session.SessionToken 25 | import com.zs.core.await 26 | 27 | private const val TAG = "PlaybackControllerImpl" 28 | 29 | // TODO: currently a quickfix requirement. find better alternative. 30 | private fun Context.browser(listener: MediaBrowser.Listener) = 31 | MediaBrowser 32 | .Builder(this, SessionToken(this, ComponentName(this, Playback::class.java))) 33 | .setListener(listener) 34 | .buildAsync() 35 | 36 | internal class PlaybackControllerImpl( 37 | val context: Context 38 | ) : PlaybackController, MediaBrowser.Listener { 39 | 40 | // TODO: A quickfix, find better alternative of doing this. 41 | 42 | // The fBrowser variable is lazily initialized with context.browser(this). 43 | // Whenever fBrowser is accessed, the getter checks if the current value is cancelled. 44 | // If it is cancelled, it re-initializes fBrowser with a new context.browser(this). 45 | // Otherwise, it retains the current value. 46 | // The goal is to ensure that fBrowser always holds a valid browser context, 47 | // reinitializing it if the current one has been cancelled. 48 | private var fBrowser = context.browser(this) 49 | get() { 50 | field = if (field.isCancelled) context.browser(this) else field 51 | return field 52 | } 53 | 54 | 55 | override suspend fun setMediaFiles(values: List): Int { 56 | val browser = fBrowser.await() 57 | // make sure the items are distinct. 58 | val list = values.distinctBy { it.mediaUri } 59 | // set the media items; this will automatically clear the old ones. 60 | browser.setMediaItems(list.map { it.value }) 61 | // return how many have been added to the list. 62 | return list.size 63 | } 64 | 65 | override suspend fun play(playWhenReady: Boolean) { 66 | val browser = fBrowser.await() 67 | browser.playWhenReady = playWhenReady 68 | browser.play() 69 | } 70 | 71 | override suspend fun clear() { 72 | val browser = fBrowser.await() 73 | browser.clearMediaItems() 74 | } 75 | 76 | override suspend fun connect(){ 77 | val browser = fBrowser.await() 78 | /*TODO: Do nothing*/ 79 | } 80 | } -------------------------------------------------------------------------------- /core/src/main/java/com/zs/core/util/PathUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 11-07-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core.util 20 | 21 | import org.jetbrains.annotations.Contract 22 | 23 | /** 24 | * A scope for [PathUtils] functions. 25 | */ 26 | object PathUtils { 27 | /** 28 | * The Unix separator character. 29 | */ 30 | const val PATH_SEPARATOR = '/' 31 | 32 | /** 33 | * The extension separator character. 34 | * @since 1.4 35 | */ 36 | const val EXTENSION_SEPARATOR = '.' 37 | 38 | const val HIDDEN_PATTERN = "/." 39 | 40 | /** 41 | * Gets the name minus the path from a full fileName. 42 | * 43 | * @param path the fileName to query 44 | * @return the name of the file without the path 45 | */ 46 | fun name(path: String): String = 47 | path.substring(path.lastIndexOf(PATH_SEPARATOR) + 1) 48 | 49 | /** 50 | * @return parent of path. 51 | */ 52 | fun parent(path: String): String = 53 | path.replace("$PATH_SEPARATOR${name(path = path)}", "") 54 | 55 | /** 56 | * Returns the file extension or null string if there is no extension. This method is a 57 | * convenience method for obtaining the extension of a url and has undefined 58 | * results for other Strings. 59 | * It is Assumed that Url is file 60 | * 61 | * @param url Url of the file 62 | * 63 | * @return extension 64 | */ 65 | fun extension(url: String): String? = 66 | if (url.contains(EXTENSION_SEPARATOR)) 67 | url.substring(url.lastIndexOf(EXTENSION_SEPARATOR) + 1).lowercase() 68 | else 69 | null 70 | 71 | /** 72 | * Checks if the file or its ancestors are hidden in System. 73 | */ 74 | @Contract(pure = true) 75 | fun areAncestorsHidden(path: String): Boolean = 76 | path.contains(HIDDEN_PATTERN) 77 | } -------------------------------------------------------------------------------- /core/src/main/java/com/zs/core/util/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 11-07-2024. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.core.util 20 | 21 | import android.media.MediaMetadataRetriever 22 | import android.util.Log 23 | import java.util.regex.Matcher 24 | import java.util.regex.Pattern 25 | 26 | private const val TAG = "Util" 27 | 28 | //language=RegExp 29 | private val ISO6709LocationPattern = Pattern.compile("([+\\-][0-9.]+)([+\\-][0-9.]+)") 30 | 31 | 32 | internal inline fun T.runCatching(tag: String, block: T.() -> R): R? { 33 | return try { 34 | block() 35 | } catch (e: Throwable) { 36 | Log.d(tag, "runCatching: $e") 37 | null 38 | } 39 | } 40 | 41 | /** 42 | * This method parses the given string representing a geographic point location by coordinates in ISO 6709 format 43 | * and returns the latitude and the longitude in float. If `location` is not in ISO 6709 format, 44 | * this method returns `null` 45 | * 46 | * @param location a String representing a geographic point location by coordinates in ISO 6709 format 47 | * @return `null` if the given string is not as expected, an array of floats with size 2, 48 | * where the first element represents latitude and the second represents longitude, otherwise. 49 | */ 50 | val MediaMetadataRetriever.latLong: DoubleArray? 51 | get() = runCatching(TAG) { 52 | val location = 53 | extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION) ?: return@runCatching null 54 | val m: Matcher = ISO6709LocationPattern.matcher(location) 55 | if (m.find() && m.groupCount() == 2) { 56 | val latstr: String = m.group(1) ?: return@runCatching null 57 | val lonstr: String = m.group(2) ?: return@runCatching null 58 | val lat = latstr.toDouble() 59 | val lon = lonstr.toDouble() 60 | doubleArrayOf(lat, lon) 61 | } else null 62 | } -------------------------------------------------------------------------------- /core/src/test/java/com/zs/core/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.core 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | preserve_hierarchy: true 2 | # project_id: 741779 3 | files: 4 | - source: "**/values/strings.xml" 5 | translation: "/values-%android_code%/%original_file_name%" #CORRECT 6 | update_strings: true 7 | cleanup_mode: true 8 | import_eq_suggestions: true 9 | auto_approve_imported: true 10 | translate_hidden: true 11 | # This will be converted to 'app/src/main/res/values-%two_letter_code%/%original_file_name%' export pattern for each file -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 8 | # When configured, Gradle will run in incubating parallel mode. 9 | # This option should only be used with decoupled projects. More details, visit 10 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 11 | android.useAndroidX=true 12 | # Kotlin code style for this project: "official" or "obsolete": 13 | kotlin.code.style=official 14 | # org.gradle.parallel=true 15 | #Fri Dec 15 20:21:04 IST 2023 16 | # Enable build config feature to specify app's build configuration 17 | # android.defaults.buildfeatures.buildconfig=true 18 | # Disable Jetifier feature to avoid compatibility issues with newer Android versions 19 | android.enableJetifier=false 20 | # Prevent caching non-final resource IDs to avoid stale resource IDs 21 | android.nonFinalResIds=false 22 | # Enable non-transitive R class feature to avoid conflicts between R classes in dependencies 23 | android.nonTransitiveRClass=true 24 | #Enable k2 to hopefully to improve autocomplete performance 25 | # kotlin.experimental.tryK2=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 19 10:22:34 IST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven(url ="https://android-sdk.is.com/") 14 | maven(url = "https://jitpack.io") 15 | } 16 | } 17 | rootProject.name = "Audiofy" 18 | include(":app") 19 | 20 | 21 | 22 | include(":codex") 23 | include(":ads") 24 | include(":core-ui") 25 | include(":core") 26 | include(":widget") 27 | -------------------------------------------------------------------------------- /stability_config.conf: -------------------------------------------------------------------------------- 1 | // Consider LocalDateTime stable 2 | //java.time.LocalDateTime 3 | // Consider my datalayer stable 4 | //com.datalayer.* 5 | // Consider my datalayer and all submodules stable 6 | //com.datalayer.** 7 | // Consider my generic type stable based off it is first type parameter only 8 | //com.example.GenericClass<*,_> 9 | // Consider NowPlaying stable; 10 | // NowPlaying is inline class and hence need the internal to be declared as stable 11 | com.zs.core.playback.NowPlaying 12 | android.content.Intent 13 | com.android.billingclient.api.Purchase 14 | com.android.billingclient.api.ProductDetails -------------------------------------------------------------------------------- /widget/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /widget/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.jetbrains.kotlin.android) 4 | alias(libs.plugins.compose.compiler) 5 | } 6 | 7 | android { 8 | namespace = "com.zs.widget" 9 | compileSdk = 35 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | release { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility = JavaVersion.VERSION_11 29 | targetCompatibility = JavaVersion.VERSION_11 30 | } 31 | kotlinOptions { jvmTarget = "11" } 32 | buildFeatures { compose = true } 33 | } 34 | 35 | dependencies { 36 | implementation(libs.glance.appwidget) 37 | implementation(libs.glance.material3) 38 | implementation(project(":core")) 39 | } -------------------------------------------------------------------------------- /widget/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/widget/consumer-rules.pro -------------------------------------------------------------------------------- /widget/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 -------------------------------------------------------------------------------- /widget/src/androidTest/java/com/zs/widget/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.widget 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.zs.widget.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /widget/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /widget/src/main/java/com/zs/widget/AppWidget.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 15-01-2025. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.widget 20 | 21 | import android.content.Context 22 | import android.util.Log 23 | import androidx.compose.runtime.collectAsState 24 | import androidx.compose.runtime.getValue 25 | import androidx.compose.runtime.remember 26 | import androidx.compose.ui.unit.dp 27 | import androidx.glance.GlanceTheme 28 | import androidx.glance.LocalSize 29 | import androidx.glance.appwidget.GlanceAppWidget 30 | import androidx.glance.appwidget.GlanceAppWidgetReceiver 31 | import androidx.glance.appwidget.SizeMode 32 | import androidx.glance.appwidget.provideContent 33 | import com.zs.core.playback.NowPlaying 34 | import com.zs.core.playback.PlaybackController 35 | import androidx.glance.GlanceId as ID 36 | 37 | private const val TAG = "AppWidget" 38 | 39 | class AppWidget : GlanceAppWidgetReceiver() { 40 | 41 | private class RealWidget() : GlanceAppWidget() { 42 | 43 | override val sizeMode: SizeMode = SizeMode.Exact 44 | 45 | override suspend fun provideGlance(context: Context, id: ID) { 46 | provideContent { 47 | GlanceTheme { 48 | val type = LocalSize.current.let { (width, height) -> 49 | when { 50 | width > 300.dp -> ViewType.NORMAL 51 | height > 100.dp -> ViewType.SQUARE 52 | else -> ViewType.COMPACT 53 | } 54 | } 55 | // Observe the playback state 56 | val state by remember { PlaybackController.observe(context) } 57 | .collectAsState(NowPlaying.EMPTY) 58 | Log.d(TAG, "$type") 59 | Log.d(TAG, "State: $state") 60 | Universal(state, type) 61 | } 62 | } 63 | } 64 | } 65 | 66 | override val glanceAppWidget: GlanceAppWidget = RealWidget() 67 | } 68 | -------------------------------------------------------------------------------- /widget/src/main/java/com/zs/widget/Util.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Zakir Sheikh 3 | * 4 | * Created by Zakir Sheikh on 20-01-2025. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.zs.widget 20 | 21 | import android.annotation.SuppressLint 22 | import android.content.Context 23 | import android.util.Log 24 | import androidx.compose.runtime.Composable 25 | import androidx.glance.LocalContext 26 | import androidx.glance.action.clickable 27 | import androidx.glance.GlanceModifier as Modifier 28 | import androidx.glance.unit.ColorProvider 29 | import java.nio.file.WatchEvent 30 | 31 | private const val TAG = "Util" 32 | 33 | /** 34 | * Creates a new `ColorProvider` with a modified alpha value. 35 | * 36 | * @param context The context needed to resolve the color. 37 | * @param alpha The new alpha value (0f-1f); -1f means use the original alpha. 38 | * @return A new `ColorProvider` or the original if alpha is -1f. 39 | */ 40 | @SuppressLint("RestrictedApi") 41 | fun ColorProvider.copy(context: Context, alpha: Float = -1f) = if (alpha == -1f) this else 42 | ColorProvider(getColor(context).copy(alpha)) 43 | 44 | @Composable 45 | internal fun Modifier.launchApp(): Modifier { 46 | val context = LocalContext.current 47 | return clickable { 48 | Log.d(TAG, "Universal: ${context.packageName}") 49 | context.startActivity(context.packageManager.getLaunchIntentForPackage(context.packageName)) 50 | } 51 | } -------------------------------------------------------------------------------- /widget/src/main/java/com/zs/widget/ViewType.kt: -------------------------------------------------------------------------------- 1 | package com.zs.widget 2 | 3 | /** 4 | * Represents the different sizes for a widget. 5 | * 6 | * This enum is used to categorize the size of a widget based on its width and height. 7 | * It provides three distinct categories: 8 | * - COMPACT: A small widget with width less than 300 and height less than 100. 9 | * - SQUARE: A widget with approximately equal width and height, or a size that doesn't fall into COMPACT or NORMAL. 10 | * - NORMAL: A larger widget with width greater than 300 and height greater than 100. 11 | */ 12 | internal enum class ViewType { 13 | COMPACT, 14 | SQUARE, 15 | NORMAL 16 | } -------------------------------------------------------------------------------- /widget/src/main/res/drawable/app_widget_preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iZakirSheikh/Audiofy/7cc20318196ab355d99863b0192abd1a5af6ba14/widget/src/main/res/drawable/app_widget_preview.webp -------------------------------------------------------------------------------- /widget/src/main/res/drawable/ic_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/ic_pause_rounded_filled.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/ic_round_play_filled.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/ic_skip_to_next.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/ic_skip_to_prev.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/media3_notification_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/media3_notification_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /widget/src/main/res/drawable/rect_rounded_cornors_12dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /widget/src/main/res/layout/chronometer.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /widget/src/main/res/layout/chronometer_bold.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /widget/src/main/res/xml/app_widget_info.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /widget/src/test/java/com/zs/widget/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.zs.widget 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } --------------------------------------------------------------------------------