├── .editorconfig ├── .github ├── dependabot.yml.bkp ├── release-drafter.yml ├── renovate.json └── workflows │ ├── build.yml │ ├── draft.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .run ├── Generate Baseline Profile.run.xml ├── Generate Compose Metrics.run.xml ├── Kover Html Debug Report.run.xml └── Run all tests.run.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── dev │ └── res │ │ ├── drawable │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ └── values │ │ ├── ic_launcher_background.xml │ │ └── strings.xml │ ├── devRelease │ └── generated │ │ └── baselineProfiles │ │ ├── baseline-prof.txt │ │ └── startup-prof.txt │ └── main │ ├── AndroidManifest.xml │ ├── baseline-prof.txt │ ├── ic_launcher-playstore.png │ ├── kotlin │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ ├── FocusLauncherApp.kt │ │ ├── LauncherActivity.kt │ │ └── di │ │ └── CircuitModule.kt │ └── res │ ├── drawable │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── ic_launcher_background.xml │ └── themes.xml ├── baselineprofile ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── baselineprofile │ ├── BaselineProfileGenerator.kt │ ├── StartupBenchmarks.kt │ └── extensions │ ├── LauncherScreenExtensions.kt │ └── UiDeviceExtensions.kt ├── build-logic ├── convention │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── AndroidApplicationComposeConventionPlugin.kt │ │ ├── AndroidApplicationConventionPlugin.kt │ │ ├── AndroidFeatureConventionPlugin.kt │ │ ├── AndroidHiltConventionPlugin.kt │ │ ├── AndroidLibraryComposeConventionPlugin.kt │ │ ├── AndroidLibraryComposeTestingConventionPlugin.kt │ │ ├── AndroidLibraryConventionPlugin.kt │ │ ├── AndroidRoomConventionPlugin.kt │ │ ├── KotlinLibraryConventionPlugin.kt │ │ ├── LintConventionPlugin.kt │ │ ├── NewScreenConventionPlugin.kt │ │ ├── SentryPlugin.kt │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ ├── AndroidCompose.kt │ │ └── KotlinAndroid.kt ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties └── settings.gradle.kts ├── build.gradle.kts ├── buildScripts └── install-git-hooks.gradle.kts ├── config └── detekt │ └── detekt.yml ├── core ├── circuitoverlay │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── circuitoverlay │ │ ├── OverlayResultScreen.kt │ │ └── bottomsheet │ │ ├── BottomSheetOverlay.kt │ │ └── BottomSheetOverlayExtensions.kt ├── common │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── core │ │ │ └── common │ │ │ ├── appcoroutinedispatcher │ │ │ ├── AppCoroutineDispatcher.kt │ │ │ ├── impl │ │ │ │ └── AppCoroutineDispatcherImpl.kt │ │ │ └── test │ │ │ │ └── TestAppCoroutineDispatcher.kt │ │ │ ├── di │ │ │ ├── CommonModule.kt │ │ │ └── test │ │ │ │ └── TestCommonModule.kt │ │ │ ├── extensions │ │ │ ├── ContextExtensions.kt │ │ │ ├── DateTimeExtensions.kt │ │ │ ├── ImmutableExtensions.kt │ │ │ └── UtilityExtensions.kt │ │ │ ├── model │ │ │ ├── LoadingState.kt │ │ │ └── State.kt │ │ │ ├── network │ │ │ ├── NetworkMonitor.kt │ │ │ ├── impl │ │ │ │ └── ConnectivityManagerNetworkMonitorImpl.kt │ │ │ └── test │ │ │ │ └── FakeNetworkMonitor.kt │ │ │ └── providers │ │ │ ├── clock │ │ │ ├── ClockProvider.kt │ │ │ ├── impl │ │ │ │ └── ClockProviderImpl.kt │ │ │ └── test │ │ │ │ └── TestClockProvider.kt │ │ │ └── randomnumber │ │ │ ├── RandomNumberProvider.kt │ │ │ ├── impl │ │ │ └── RandomNumberProviderImpl.kt │ │ │ └── test │ │ │ └── TestRandomNumberProvider.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── common │ │ └── extensions │ │ ├── FormatNumberTest.kt │ │ └── ImmutableExtensionsKtTest.kt ├── data-test │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── data │ │ └── test │ │ └── repository │ │ ├── FakeAppDrawerRepo.kt │ │ ├── FakeClockRepo.kt │ │ ├── FakeFavoritesRepo.kt │ │ ├── FakeHiddenAppsRepo.kt │ │ ├── FakeLunarPhaseDetailsRepo.kt │ │ ├── FakePlacesRepo.kt │ │ ├── FakeQuotesRepo.kt │ │ ├── FakeThemeRepo.kt │ │ └── settings │ │ ├── FakeAppDrawerSettingsRepo.kt │ │ ├── FakeClockSettingsRepo.kt │ │ ├── FakeGeneralSettingsRepo.kt │ │ ├── FakeLunarPhaseSettingsRepo.kt │ │ └── FakeQuotesSettingsRepo.kt ├── data │ ├── .gitignore │ ├── build.gradle.kts │ ├── schemas │ │ └── dev.mslalith.focuslauncher.core.data.database.AppDatabase │ │ │ ├── 1.json │ │ │ ├── 2.json │ │ │ └── 3.json │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── core │ │ │ └── data │ │ │ ├── database │ │ │ ├── AppDatabase.kt │ │ │ ├── dao │ │ │ │ ├── AppsDao.kt │ │ │ │ ├── FavoriteAppsDao.kt │ │ │ │ ├── HiddenAppsDao.kt │ │ │ │ ├── PlacesDao.kt │ │ │ │ └── QuotesDao.kt │ │ │ ├── entities │ │ │ │ ├── AppRoom.kt │ │ │ │ ├── FavoriteAppRoom.kt │ │ │ │ ├── HiddenAppRoom.kt │ │ │ │ ├── PlaceRoom.kt │ │ │ │ └── QuoteRoom.kt │ │ │ ├── typeconverter │ │ │ │ └── Converters.kt │ │ │ └── usecase │ │ │ │ ├── datastore │ │ │ │ ├── ClearDataStoreUseCase.kt │ │ │ │ └── ClearThemeDataStoreUseCase.kt │ │ │ │ └── room │ │ │ │ └── CloseDatabaseUseCase.kt │ │ │ ├── di │ │ │ └── modules │ │ │ │ ├── DataStoreModule.kt │ │ │ │ ├── NetworkModule.kt │ │ │ │ ├── RepositoryModule.kt │ │ │ │ ├── RoomModule.kt │ │ │ │ └── test │ │ │ │ ├── TestNetworkModule.kt │ │ │ │ ├── TestRepositoryModule.kt │ │ │ │ └── TestRoomModule.kt │ │ │ ├── dto │ │ │ ├── AddressDto.kt │ │ │ ├── AppDto.kt │ │ │ ├── PlaceDto.kt │ │ │ └── QuoteDto.kt │ │ │ ├── network │ │ │ ├── api │ │ │ │ ├── PlacesApi.kt │ │ │ │ ├── QuotesApi.kt │ │ │ │ ├── fakes │ │ │ │ │ ├── FakePlacesApi.kt │ │ │ │ │ └── FakeQuotesApi.kt │ │ │ │ └── impl │ │ │ │ │ ├── PlacesApiImpl.kt │ │ │ │ │ └── QuotesApiImpl.kt │ │ │ └── entities │ │ │ │ ├── PlaceResponse.kt │ │ │ │ └── QuoteResponse.kt │ │ │ ├── repository │ │ │ ├── AppDrawerRepo.kt │ │ │ ├── ClockRepo.kt │ │ │ ├── FavoritesRepo.kt │ │ │ ├── HiddenAppsRepo.kt │ │ │ ├── LunarPhaseDetailsRepo.kt │ │ │ ├── PlacesRepo.kt │ │ │ ├── QuotesRepo.kt │ │ │ ├── ThemeRepo.kt │ │ │ ├── impl │ │ │ │ ├── AppDrawerRepoImpl.kt │ │ │ │ ├── ClockRepoImpl.kt │ │ │ │ ├── FavoritesRepoImpl.kt │ │ │ │ ├── HiddenAppsRepoImpl.kt │ │ │ │ ├── LunarPhaseDetailsRepoImpl.kt │ │ │ │ ├── PlacesRepoImpl.kt │ │ │ │ ├── QuotesRepoImpl.kt │ │ │ │ ├── ThemeRepoImpl.kt │ │ │ │ └── settings │ │ │ │ │ ├── AppDrawerSettingsRepoImpl.kt │ │ │ │ │ ├── ClockSettingsRepoImpl.kt │ │ │ │ │ ├── GeneralSettingsRepoImpl.kt │ │ │ │ │ ├── LunarPhaseSettingsRepoImpl.kt │ │ │ │ │ └── QuotesSettingsRepoImpl.kt │ │ │ └── settings │ │ │ │ ├── AppDrawerSettingsRepo.kt │ │ │ │ ├── ClockSettingsRepo.kt │ │ │ │ ├── GeneralSettingsRepo.kt │ │ │ │ ├── LunarPhaseSettingsRepo.kt │ │ │ │ └── QuotesSettingsRepo.kt │ │ │ └── utils │ │ │ ├── PlacesRepoHelpers.kt │ │ │ └── QuotesRepoHelpers.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── data │ │ ├── database │ │ ├── MigrationTest.kt │ │ └── usecase │ │ │ └── room │ │ │ └── CloseDatabaseUseCaseTest.kt │ │ ├── helpers │ │ └── SettingsRepoHelpers.kt │ │ ├── network │ │ └── api │ │ │ └── impl │ │ │ ├── PlacesApiImplTest.kt │ │ │ └── QuotesApiImplTest.kt │ │ └── repository │ │ ├── AppDrawerRepoTest.kt │ │ ├── ClockRepoTest.kt │ │ ├── FavoritesRepoTest.kt │ │ ├── HiddenAppsRepoTest.kt │ │ ├── LunarPhaseDetailsRepoTest.kt │ │ ├── PlacesRepoTest.kt │ │ ├── QuotesRepoTest.kt │ │ ├── ThemeRepoTest.kt │ │ └── settings │ │ ├── AppDrawerSettingsRepoTest.kt │ │ ├── ClockSettingsRepoTest.kt │ │ ├── GeneralSettingsRepoTest.kt │ │ ├── LunarPhaseSettingsRepoTest.kt │ │ └── QuotesSettingsRepoTest.kt ├── domain │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── core │ │ │ └── domain │ │ │ ├── PackageActionUseCase.kt │ │ │ ├── appdrawer │ │ │ └── GetAppDrawerAppsUseCase.kt │ │ │ ├── apps │ │ │ ├── GetAllAppsOnIconPackChangeUseCase.kt │ │ │ ├── GetAppDrawerIconicAppsUseCase.kt │ │ │ ├── GetFavoriteColoredAppsUseCase.kt │ │ │ ├── GetIconPackIconicAppsUseCase.kt │ │ │ └── core │ │ │ │ ├── GetAppsIconPackAwareUseCase.kt │ │ │ │ └── GetAppsUseCase.kt │ │ │ ├── extensions │ │ │ └── AppExtensions.kt │ │ │ ├── iconpack │ │ │ ├── FetchIconPacksUseCase.kt │ │ │ ├── GetIconPackAppsUseCase.kt │ │ │ ├── LoadIconPackUseCase.kt │ │ │ ├── ReloadIconPackAfterFirstLoadUseCase.kt │ │ │ └── ReloadIconPackUseCase.kt │ │ │ ├── launcherapps │ │ │ ├── GetDefaultFavoriteAppsUseCase.kt │ │ │ └── LoadAllAppsUseCase.kt │ │ │ ├── settings │ │ │ └── UpdateReportCrashesSettingUseCase.kt │ │ │ └── theme │ │ │ ├── ChangeThemeUseCase.kt │ │ │ └── GetThemeUseCase.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── domain │ │ ├── apps │ │ ├── GetAllAppsOnIconPackChangeUseCaseTest.kt │ │ ├── GetIconPackIconicAppsUseCaseTest.kt │ │ └── PackageActionUseCaseTest.kt │ │ ├── iconpack │ │ ├── FetchIconPacksUseCaseTest.kt │ │ ├── GetIconPackAppsUseCaseTest.kt │ │ ├── LoadIconPackUseCaseTest.kt │ │ ├── ReloadIconPackAfterFirstLoadUseCaseTest.kt │ │ └── ReloadIconPackUseCaseTest.kt │ │ ├── launcherapps │ │ └── LoadAllAppsUseCaseTest.kt │ │ ├── theme │ │ ├── ChangeThemeUseCaseTest.kt │ │ └── GetThemeUseCaseTest.kt │ │ └── utils │ │ └── Extensions.kt ├── launcherapps │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── core │ │ │ └── launcherapps │ │ │ ├── di │ │ │ ├── LauncherAppsModule.kt │ │ │ └── test │ │ │ │ └── TestLauncherAppsModule.kt │ │ │ ├── manager │ │ │ ├── iconcache │ │ │ │ ├── IconCacheManager.kt │ │ │ │ └── IconCacheManagerImpl.kt │ │ │ ├── iconpack │ │ │ │ ├── IconPackManager.kt │ │ │ │ ├── impl │ │ │ │ │ └── IconPackManagerImpl.kt │ │ │ │ └── test │ │ │ │ │ └── TestIconPackManager.kt │ │ │ └── launcherapps │ │ │ │ ├── LauncherAppsManager.kt │ │ │ │ ├── impl │ │ │ │ └── LauncherAppsManagerImpl.kt │ │ │ │ └── test │ │ │ │ └── TestLauncherAppsManager.kt │ │ │ ├── model │ │ │ ├── DrawableInfo.kt │ │ │ └── IconPack.kt │ │ │ ├── parser │ │ │ └── IconPackXmlParser.kt │ │ │ ├── providers │ │ │ └── icons │ │ │ │ ├── IconProvider.kt │ │ │ │ ├── impl │ │ │ │ └── IconProviderImpl.kt │ │ │ │ └── test │ │ │ │ └── TestIconProvider.kt │ │ │ └── utils │ │ │ └── GoogleCalendarIcon.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── launcherapps │ │ └── providers │ │ └── icons │ │ └── impl │ │ └── IconProviderImplTest.kt ├── lint │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── lint │ │ ├── detekt │ │ ├── IgnoreCyclomaticComplexMethod.kt │ │ ├── IgnoreLongMethod.kt │ │ └── IgnoreNestedBlockDepth.kt │ │ └── kover │ │ └── IgnoreInKoverReport.kt ├── model │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── model │ │ ├── AppDrawerViewType.kt │ │ ├── BuildFlavor.kt │ │ ├── ClockAlignment.kt │ │ ├── ConfirmSelectableItemType.kt │ │ ├── Constants.kt │ │ ├── CurrentPlace.kt │ │ ├── IconPackLoadEvent.kt │ │ ├── IconPackType.kt │ │ ├── PackageAction.kt │ │ ├── Quote.kt │ │ ├── Theme.kt │ │ ├── UiText.kt │ │ ├── WidgetType.kt │ │ ├── app │ │ ├── App.kt │ │ ├── AppWithColor.kt │ │ ├── AppWithComponent.kt │ │ ├── AppWithIcon.kt │ │ ├── SelectedApp.kt │ │ └── SelectedHiddenApp.kt │ │ ├── appdrawer │ │ ├── AppDrawerIconViewType.kt │ │ └── AppDrawerItem.kt │ │ ├── extensions │ │ └── UtilityExtensions.kt │ │ ├── location │ │ └── LatLng.kt │ │ ├── lunarphase │ │ ├── LunarPhase.kt │ │ ├── LunarPhaseDetails.kt │ │ ├── LunarPhaseDirection.kt │ │ ├── NextPhaseDetails.kt │ │ ├── RiseAndSetDetails.kt │ │ └── UpcomingLunarPhase.kt │ │ └── place │ │ ├── Address.kt │ │ └── Place.kt ├── resources │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── res │ │ ├── drawable │ │ ├── ic_align_horizontal_center.xml │ │ ├── ic_align_horizontal_left.xml │ │ ├── ic_align_horizontal_right.xml │ │ ├── ic_app_drawer_colored.xml │ │ ├── ic_app_drawer_icons.xml │ │ ├── ic_app_drawer_text.xml │ │ ├── ic_arrow_left.xml │ │ ├── ic_broom.xml │ │ ├── ic_check.xml │ │ ├── ic_close.xml │ │ ├── ic_delete.xml │ │ ├── ic_device_mobile.xml │ │ ├── ic_drag_indicator.xml │ │ ├── ic_edit.xml │ │ ├── ic_grid.xml │ │ ├── ic_hand_swipe_left.xml │ │ ├── ic_heart.xml │ │ ├── ic_house.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_list.xml │ │ ├── ic_logo_github.xml │ │ ├── ic_logo_phosphor.xml │ │ ├── ic_logo_twitter.xml │ │ ├── ic_map_pin.xml │ │ ├── ic_map_pin_line.xml │ │ ├── ic_moon_stars.xml │ │ ├── ic_quote.xml │ │ ├── ic_search.xml │ │ ├── ic_star.xml │ │ ├── ic_star_outline.xml │ │ ├── ic_sun_dim.xml │ │ └── ic_visibility_off.xml │ │ └── values │ │ ├── ic_launcher_background.xml │ │ └── strings.xml ├── screens │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── screens │ │ ├── BottomSheetScreens.kt │ │ ├── Screens.kt │ │ └── UiComponentScreens.kt ├── settings │ └── sentry │ │ ├── build.gradle.kts │ │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── settings │ │ └── sentry │ │ ├── FakeSentrySettings.kt │ │ ├── SentrySettings.kt │ │ ├── SentrySettingsImpl.kt │ │ └── di │ │ ├── SentryModule.kt │ │ └── TestSentryModule.kt ├── testing-circuit │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── testing │ │ └── circuit │ │ └── PresenterTest.kt ├── testing-compose │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── testing │ │ └── compose │ │ ├── TestSemanticsProperties.kt │ │ ├── assertion │ │ ├── AssertBiasAlignment.kt │ │ ├── AssertPrimitiveTypes.kt │ │ ├── AssertSelectedApp.kt │ │ ├── AssertSelectedHiddenApp.kt │ │ ├── AssertWidgetType.kt │ │ └── WaitAndAssertIsDisplayed.kt │ │ ├── extensions │ │ └── GeneralExtensions.kt │ │ ├── matcher │ │ ├── GeneralMatchers.kt │ │ ├── MatcherPrimitiveTypes.kt │ │ ├── MatcherSelectedApp.kt │ │ ├── MatcherSelectedHiddenApp.kt │ │ └── MatcherWidgetType.kt │ │ ├── modifier │ │ └── testsemantics │ │ │ ├── TestSemanticsModifier.kt │ │ │ ├── TestSemanticsScope.kt │ │ │ └── impl │ │ │ └── TestSemanticsScopeImpl.kt │ │ └── waiter │ │ ├── GeneralWaiters.kt │ │ ├── WaitPrimitiveTypes.kt │ │ └── WaitSelectedApps.kt ├── testing │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── core │ │ └── testing │ │ ├── AppRobolectricTestRunner.kt │ │ ├── CoroutineTest.kt │ │ ├── HiltTestRunner.kt │ │ ├── KtorApiTest.kt │ │ ├── TestApps.kt │ │ ├── extensions │ │ ├── DateTimeExtensions.kt │ │ ├── LocaleExtensions.kt │ │ ├── TurbineAssertExtensions.kt │ │ └── TurbineExtensions.kt │ │ └── rules │ │ └── TestCoroutineRule.kt └── ui │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── dev │ └── mslalith │ └── focuslauncher │ └── core │ └── ui │ ├── ActionButton.kt │ ├── AppBarWithBackIcon.kt │ ├── Blob.kt │ ├── ChooserGroup.kt │ ├── ConfirmSelectableItem.kt │ ├── DotWaveLoader.kt │ ├── RoundIcon.kt │ ├── SearchField.kt │ ├── SelectableCheckboxItem.kt │ ├── SelectableIconItem.kt │ ├── Spacer.kt │ ├── TextButton.kt │ ├── TextIconButton.kt │ ├── controller │ └── SystemUiController.kt │ ├── effects │ ├── OnDayChangeListener.kt │ ├── OnLifecycleEventChange.kt │ ├── PackageActionListener.kt │ └── SystemBroadcastReceiver.kt │ ├── extensions │ ├── ColorExtensions.kt │ ├── ModifierExtensions.kt │ ├── ScaffoldExtensions.kt │ └── UiTextExtensions.kt │ ├── modifiers │ ├── HorizontalFadeOutEdge.kt │ └── VerticalFadeOutEdge.kt │ ├── providers │ ├── ProvideLauncherPagerState.kt │ └── ProvideSystemUiController.kt │ ├── remember │ └── RememberAppColor.kt │ └── settings │ ├── SettingsLoadableItem.kt │ ├── SettingsSelectableBottomContentItem.kt │ ├── SettingsSelectableChooserItem.kt │ ├── SettingsSelectableItem.kt │ ├── SettingsSelectableSliderItem.kt │ └── SettingsSelectableSwitchItem.kt ├── feature ├── appdrawerpage │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── appdrawerpage │ │ │ ├── AppDrawerPage.kt │ │ │ ├── AppDrawerPageContract.kt │ │ │ ├── AppDrawerPagePresenter.kt │ │ │ ├── apps │ │ │ ├── grid │ │ │ │ ├── AppDrawerGridItem.kt │ │ │ │ ├── AppsGrid.kt │ │ │ │ └── PreviewAppsGrid.kt │ │ │ └── list │ │ │ │ ├── AppDrawerListItem.kt │ │ │ │ ├── AppsList.kt │ │ │ │ ├── CharacterHeader.kt │ │ │ │ └── GroupedAppsList.kt │ │ │ ├── bottomsheet │ │ │ ├── moreoptions │ │ │ │ ├── AppMoreOptionsBottomSheet.kt │ │ │ │ ├── AppMoreOptionsBottomSheetContract.kt │ │ │ │ └── AppMoreOptionsBottomSheetPresenter.kt │ │ │ └── updateappdisplayname │ │ │ │ ├── UpdateAppDisplayNameBottomSheet.kt │ │ │ │ ├── UpdateAppDisplayNameBottomSheetContract.kt │ │ │ │ └── UpdateAppDisplayNameBottomSheetPresenter.kt │ │ │ └── utils │ │ │ └── Constants.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── appdrawerpage │ │ ├── AppDrawerPagePresenterTest.kt │ │ └── bottomsheet │ │ ├── moreoptions │ │ └── AppMoreOptionsBottomSheetPresenterTest.kt │ │ └── updateappdisplayname │ │ └── UpdateAppDisplayNameBottomSheetPresenterTest.kt ├── clock24 │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── clock24 │ │ │ ├── bottomsheet │ │ │ └── clockwidgetsettings │ │ │ │ ├── ClockWidgetSettingsBottomSheet.kt │ │ │ │ ├── ClockWidgetSettingsBottomSheetContract.kt │ │ │ │ └── ClockWidgetSettingsBottomSheetPresenter.kt │ │ │ ├── model │ │ │ ├── AnalogClockHandlePhase.kt │ │ │ ├── AnalogClockPhase.kt │ │ │ └── Digit.kt │ │ │ ├── settings │ │ │ └── PreviewClock.kt │ │ │ ├── utils │ │ │ └── TestTags.kt │ │ │ └── widget │ │ │ ├── ClockWidgetUiComponent.kt │ │ │ ├── ClockWidgetUiComponentContract.kt │ │ │ ├── ClockWidgetUiComponentPresenter.kt │ │ │ └── ui │ │ │ ├── Clock24.kt │ │ │ └── CurrentTime.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── clock24 │ │ ├── ClockWidgetKtTest.kt │ │ ├── CurrentTimeKtTest.kt │ │ ├── bottomsheet │ │ └── clockwidgetsettings │ │ │ └── ClockWidgetSettingsBottomSheetPresenterTest.kt │ │ └── widget │ │ └── ClockWidgetUiComponentPresenterTest.kt ├── favorites │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── favorites │ │ │ ├── FavoritesListUiComponent.kt │ │ │ ├── FavoritesListUiComponentContract.kt │ │ │ ├── FavoritesListUiComponentPresenter.kt │ │ │ ├── bottomsheet │ │ │ ├── DismissibleFavoriteItem.kt │ │ │ ├── FavoritesBottomSheet.kt │ │ │ ├── FavoritesBottomSheetContract.kt │ │ │ └── FavoritesBottomSheetPresenter.kt │ │ │ └── ui │ │ │ └── StaggeredFlowRow.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── favorites │ │ ├── FavoritesListUiComponentPresenterTest.kt │ │ └── bottomsheet │ │ └── FavoritesBottomSheetPresenterTest.kt ├── homepage │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── homepage │ │ ├── DecoratedLunarCalendar.kt │ │ ├── DecoratedQuote.kt │ │ ├── HomePage.kt │ │ ├── HomePageContract.kt │ │ ├── HomePagePresenter.kt │ │ └── model │ │ └── HomePadding.kt ├── lunarcalendar │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── lunarcalendar │ │ │ ├── bottomsheet │ │ │ ├── lunarphasedetails │ │ │ │ ├── LunarPhaseDetailsBottomSheet.kt │ │ │ │ ├── LunarPhaseDetailsBottomSheetContract.kt │ │ │ │ ├── LunarPhaseDetailsBottomSheetPresenter.kt │ │ │ │ └── ui │ │ │ │ │ ├── LunarRiseAndSetDetails.kt │ │ │ │ │ ├── NextMajorPhaseDetails.kt │ │ │ │ │ ├── NextSingleMajorPhaseDetails.kt │ │ │ │ │ ├── RiseAndSetHeaders.kt │ │ │ │ │ ├── RiseAndSetIndicator.kt │ │ │ │ │ ├── RiseAndSetTime.kt │ │ │ │ │ ├── RiseTimeDetails.kt │ │ │ │ │ ├── SetTimeDetails.kt │ │ │ │ │ ├── TodayLunarMoonIconAndPhase.kt │ │ │ │ │ ├── TodayLunarMoonPhaseDetails.kt │ │ │ │ │ └── TodayLunarPhase.kt │ │ │ └── lunarphasewidgetsettings │ │ │ │ ├── LunarPhaseWidgetSettingsBottomSheet.kt │ │ │ │ ├── LunarPhaseWidgetSettingsBottomSheetContract.kt │ │ │ │ └── LunarPhaseWidgetSettingsBottomSheetPresenter.kt │ │ │ ├── settings │ │ │ └── PreviewLunarCalendar.kt │ │ │ ├── shared │ │ │ └── LunarPhaseMoonIcon.kt │ │ │ └── widget │ │ │ ├── LunarCalendarUiComponent.kt │ │ │ ├── LunarCalendarUiComponentContract.kt │ │ │ ├── LunarCalendarUiComponentPresenter.kt │ │ │ └── ui │ │ │ ├── LunarCalendarContent.kt │ │ │ ├── LunarPhaseName.kt │ │ │ └── UpcomingLunarPhaseDetails.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── lunarcalendar │ │ ├── bottomsheet │ │ ├── lunarphasedetails │ │ │ └── LunarPhaseDetailsBottomSheetPresenterTest.kt │ │ └── lunarphasewidgetsettings │ │ │ └── LunarPhaseWidgetSettingsBottomSheetPresenterTest.kt │ │ └── widget │ │ └── LunarCalendarUiComponentPresenterTest.kt ├── quoteforyou │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── quoteforyou │ │ │ ├── bottomsheet │ │ │ └── quotewidgetsettings │ │ │ │ ├── QuoteWidgetSettingsBottomSheet.kt │ │ │ │ ├── QuoteWidgetSettingsBottomSheetContract.kt │ │ │ │ └── QuoteWidgetSettingsBottomSheetPresenter.kt │ │ │ ├── settings │ │ │ └── PreviewQuotes.kt │ │ │ └── widget │ │ │ ├── QuoteForYouUiComponent.kt │ │ │ ├── QuoteForYouUiComponentContract.kt │ │ │ ├── QuoteForYouUiComponentPresenter.kt │ │ │ └── ui │ │ │ └── QuoteForYouContent.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── quoteforyou │ │ ├── bottomsheet │ │ └── quotewidgetsettings │ │ │ └── QuoteWidgetSettingsBottomSheetPresenterTest.kt │ │ └── widget │ │ └── QuoteForYouUiComponentPresenterTest.kt ├── settingspage │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── settingspage │ │ │ ├── SettingsPage.kt │ │ │ ├── SettingsPageContract.kt │ │ │ ├── SettingsPagePresenter.kt │ │ │ ├── bottomsheet │ │ │ ├── appdrawersettings │ │ │ │ ├── AppDrawerSettingsBottomSheet.kt │ │ │ │ ├── AppDrawerSettingsBottomSheetContract.kt │ │ │ │ └── AppDrawerSettingsBottomSheetPresenter.kt │ │ │ └── privacy │ │ │ │ ├── PrivacySettingsBottomSheet.kt │ │ │ │ ├── PrivacySettingsBottomSheetContract.kt │ │ │ │ └── PrivacySettingsBottomSheetPresenter.kt │ │ │ ├── settingsitems │ │ │ ├── About.kt │ │ │ ├── AppDrawer.kt │ │ │ ├── ChangeTheme.kt │ │ │ ├── Developer.kt │ │ │ ├── EditFavorites.kt │ │ │ ├── HideApps.kt │ │ │ ├── IconPack.kt │ │ │ ├── Privacy.kt │ │ │ ├── PullDownNotifications.kt │ │ │ ├── SetAsDefaultLauncher.kt │ │ │ ├── SettingsHeader.kt │ │ │ ├── ToggleStatusBar.kt │ │ │ └── Widgets.kt │ │ │ ├── shared │ │ │ ├── SettingsExpandableItem.kt │ │ │ ├── SettingsGridContent.kt │ │ │ ├── SettingsGridItem.kt │ │ │ └── SettingsItem.kt │ │ │ └── utils │ │ │ ├── Extensions.kt │ │ │ └── TestTags.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── feature │ │ └── settingspage │ │ ├── AppDrawerSettingsSheetKtTest.kt │ │ ├── SettingsPageKtTest.kt │ │ ├── SettingsPagePresenterTest.kt │ │ └── bottomsheet │ │ ├── appdrawersettings │ │ └── AppDrawerSettingsBottomSheetPresenterTest.kt │ │ └── privacy │ │ └── PrivacySettingsBottomSheetPresenterTest.kt └── theme │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── main │ ├── kotlin │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── feature │ │ │ └── theme │ │ │ ├── LauncherTheme.kt │ │ │ ├── LauncherThemeContract.kt │ │ │ ├── LauncherThemePresenter.kt │ │ │ ├── bottomsheet │ │ │ └── themeselection │ │ │ │ ├── ThemeSelectionBottomSheet.kt │ │ │ │ ├── ThemeSelectionBottomSheetContract.kt │ │ │ │ └── ThemeSelectionBottomSheetPresenter.kt │ │ │ ├── data │ │ │ ├── Color.kt │ │ │ ├── ColorScheme.kt │ │ │ └── Type.kt │ │ │ └── model │ │ │ └── ThemeWithIcon.kt │ └── res │ │ └── font │ │ └── nunitosans_regular.ttf │ └── test │ └── kotlin │ └── dev │ └── mslalith │ └── focuslauncher │ └── feature │ └── theme │ ├── LauncherThemePresenterTest.kt │ └── bottomsheet │ └── themeselection │ └── ThemeSelectionBottomSheetPresenterTest.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── animated_clock.mp4 ├── change_theme.mp4 ├── edit_favorites.mp4 ├── hide_apps.mp4 ├── icon_pack.mp4 ├── lunar_phase_info.mp4 └── what_is.png ├── privacy-policy.html ├── screens ├── about │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── screens │ │ │ └── about │ │ │ ├── About.kt │ │ │ ├── AboutContract.kt │ │ │ ├── AboutPresenter.kt │ │ │ └── ui │ │ │ ├── AppInfo.kt │ │ │ ├── Credits.kt │ │ │ ├── MadeWithLove.kt │ │ │ └── SocialAccounts.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── about │ │ └── AboutPresenterTest.kt ├── currentplace │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── screens │ │ │ └── currentplace │ │ │ ├── CurrentPlace.kt │ │ │ ├── CurrentPlaceContract.kt │ │ │ ├── CurrentPlacePresenter.kt │ │ │ └── ui │ │ │ ├── CurrentPlaceInfo.kt │ │ │ ├── MapView.kt │ │ │ └── interop │ │ │ └── AndroidMapView.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── currentplace │ │ └── CurrentPlacePresenterTest.kt ├── developer │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── developer │ │ ├── Developer.kt │ │ ├── DeveloperContract.kt │ │ ├── DeveloperPresenter.kt │ │ └── file │ │ ├── CacheFileInteractor.kt │ │ └── FavoritesCacheFileInteractor.kt ├── editfavorites │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── screens │ │ │ └── editfavorites │ │ │ ├── EditFavorites.kt │ │ │ ├── EditFavoritesContract.kt │ │ │ ├── EditFavoritesPresenter.kt │ │ │ ├── ui │ │ │ ├── FavoriteListItem.kt │ │ │ ├── FavoritesList.kt │ │ │ └── HiddenAppActionText.kt │ │ │ └── utils │ │ │ └── TestTags.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── editfavorites │ │ ├── EditFavoritesPresenterTest.kt │ │ └── EditFavoritesScreenKtTest.kt ├── hideapps │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── screens │ │ │ └── hideapps │ │ │ ├── HideApps.kt │ │ │ ├── HideAppsContract.kt │ │ │ ├── HideAppsPresenter.kt │ │ │ ├── ui │ │ │ ├── HiddenAppListItem.kt │ │ │ └── HiddenAppsList.kt │ │ │ └── utils │ │ │ └── TestTags.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── hideapps │ │ ├── HideAppsPresenterTest.kt │ │ └── HideAppsScreenKtTest.kt ├── iconpack │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── kotlin │ │ │ └── dev │ │ │ └── mslalith │ │ │ └── focuslauncher │ │ │ └── screens │ │ │ └── iconpack │ │ │ ├── IconPack.kt │ │ │ ├── IconPackContract.kt │ │ │ └── IconPackPresenter.kt │ │ └── test │ │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── iconpack │ │ └── IconPackPresenterTest.kt └── launcher │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── main │ └── kotlin │ │ └── dev │ │ └── mslalith │ │ └── focuslauncher │ │ └── screens │ │ └── launcher │ │ ├── Launcher.kt │ │ ├── LauncherContract.kt │ │ └── LauncherPresenter.kt │ └── test │ └── kotlin │ └── dev │ └── mslalith │ └── focuslauncher │ └── screens │ └── launcher │ └── LauncherPresenterTest.kt ├── scripts └── pre-push └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{kt,kts}] 4 | charset = utf-8 5 | max_line_length = 200 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml.bkp: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | target-branch: "develop" 8 | reviewers: 9 | - "mslalith" 10 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | template: | 4 | # What's Changed 5 | 6 | $CHANGES 7 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "baseBranches": [ 7 | "develop" 8 | ], 9 | "timezone": "Asia/Kolkata", 10 | "assignees": [ 11 | "mslalith" 12 | ], 13 | "packageRules": [ 14 | { 15 | "groupName": "Kotlin", 16 | "groupSlug": "kotlin", 17 | "matchPackagePrefixes": [ 18 | "com.google.devtools.ksp", 19 | "androidx.compose.compiler" 20 | ], 21 | "matchPackagePatterns": [ 22 | "org.jetbrains.kotlin.*" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/draft.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | pull_request_target: 9 | types: [ opened, reopened, synchronize ] 10 | 11 | jobs: 12 | update_release_draft: 13 | permissions: 14 | contents: write 15 | pull-requests: read 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: release-drafter/release-drafter@v6.0.0 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | build-cache 3 | 4 | # Android Studio provided 5 | .DS_Store 6 | /app/release 7 | 8 | # Created by https://www.toptal.com/developers/gitignore/api/android 9 | # Edit at https://www.toptal.com/developers/gitignore?templates=android 10 | 11 | ### Android ### 12 | # Gradle files 13 | .gradle/ 14 | build/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Log/OS Files 20 | *.log 21 | 22 | # Android Studio generated files and folders 23 | captures/ 24 | .externalNativeBuild/ 25 | .cxx/ 26 | *.apk 27 | output.json 28 | 29 | # IntelliJ 30 | *.iml 31 | .idea/ 32 | 33 | # Keystore files 34 | *.jks 35 | *.keystore 36 | 37 | # Google Services (e.g. APIs or Firebase) 38 | app/google-services.json 39 | 40 | # Android Profiling 41 | *.hprof 42 | 43 | ### Android Patch ### 44 | gen-external-apklibs 45 | 46 | # Replacement of .externalNativeBuild directories introduced 47 | # with Android Studio 3.5. 48 | 49 | # End of https://www.toptal.com/developers/gitignore/api/android 50 | 51 | # Sentry Config File 52 | sentry.properties 53 | -------------------------------------------------------------------------------- /.run/Generate Compose Metrics.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | true 20 | false 21 | 22 | 23 | -------------------------------------------------------------------------------- /.run/Kover Html Debug Report.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Run all tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 18 | 20 | true 21 | true 22 | false 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | -keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | # Firebase 24 | #-keep class com.google.firebase.** { *; } 25 | 26 | -dontwarn edu.umd.cs.findbugs.annotations.Nullable 27 | -dontwarn org.slf4j.impl.StaticLoggerBinder 28 | 29 | -------------------------------------------------------------------------------- /app/src/dev/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/dev/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/dev/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/dev/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #313131 4 | -------------------------------------------------------------------------------- /app/src/dev/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Focus Launcher (Dev) 4 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/kotlin/dev/mslalith/focuslauncher/FocusLauncherApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.ProcessLifecycleOwner 5 | import androidx.lifecycle.lifecycleScope 6 | import dagger.hilt.android.HiltAndroidApp 7 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 8 | import dev.mslalith.focuslauncher.core.settings.sentry.SentrySettings 9 | import kotlinx.coroutines.flow.first 10 | import kotlinx.coroutines.launch 11 | import javax.inject.Inject 12 | 13 | @HiltAndroidApp 14 | @IgnoreInKoverReport 15 | class FocusLauncherApp : Application() { 16 | 17 | @Inject 18 | lateinit var sentrySettings: SentrySettings 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | 23 | setupSentry() 24 | } 25 | 26 | private fun setupSentry() = with(sentrySettings) { 27 | ProcessLifecycleOwner.get().lifecycleScope.launch { 28 | if (isEnabled.first()) enableSentry() else disableSentry() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/dev/mslalith/focuslauncher/di/CircuitModule.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.di 2 | 3 | import com.slack.circuit.foundation.Circuit 4 | import com.slack.circuit.runtime.presenter.Presenter 5 | import com.slack.circuit.runtime.ui.Ui 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object CircuitModule { 15 | 16 | @Provides 17 | @Singleton 18 | fun provideCircuit( 19 | presenterFactories: @JvmSuppressWildcards Set, 20 | uiFactories: @JvmSuppressWildcards Set 21 | ): Circuit = Circuit.Builder() 22 | .addPresenterFactories(presenterFactories) 23 | .addUiFactories(uiFactories) 24 | .build() 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #313131 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /baselineprofile/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /baselineprofile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /baselineprofile/src/main/kotlin/baselineprofile/extensions/LauncherScreenExtensions.kt: -------------------------------------------------------------------------------- 1 | package baselineprofile.extensions 2 | 3 | import androidx.benchmark.macro.MacrobenchmarkScope 4 | import androidx.test.uiautomator.By 5 | import androidx.test.uiautomator.Direction 6 | import androidx.test.uiautomator.Until 7 | 8 | internal fun MacrobenchmarkScope.gotoSettings() { 9 | device.rootObject.fling(Direction.LEFT) 10 | device.wait(Until.hasObject(By.text("Change Theme")), 5_000) 11 | } 12 | 13 | internal fun MacrobenchmarkScope.gotoHomeFromSettings() { 14 | device.rootObject.fling(Direction.RIGHT) 15 | device.wait(Until.hasObject(By.text("Full Moon")), 5_000) 16 | } 17 | 18 | internal fun MacrobenchmarkScope.gotoHomeFromAppDrawer() { 19 | device.rootObject.fling(Direction.LEFT) 20 | device.wait(Until.hasObject(By.text("Full Moon")), 5_000) 21 | } 22 | 23 | internal fun MacrobenchmarkScope.gotoAppDrawer() { 24 | device.rootObject.fling(Direction.RIGHT) 25 | device.wait(Until.hasObject(By.text("Chrome")), 5_000) 26 | } 27 | -------------------------------------------------------------------------------- /baselineprofile/src/main/kotlin/baselineprofile/extensions/UiDeviceExtensions.kt: -------------------------------------------------------------------------------- 1 | package baselineprofile.extensions 2 | 3 | import androidx.test.uiautomator.By 4 | import androidx.test.uiautomator.UiDevice 5 | import androidx.test.uiautomator.UiObject2 6 | 7 | internal val UiDevice.rootObject: UiObject2 8 | get() = findObject(By.scrollable(true)) 9 | -------------------------------------------------------------------------------- /build-logic/convention/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | import com.android.build.api.dsl.ApplicationExtension 2 | import dev.mslalith.focuslauncher.configureAndroidCompose 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.kotlin.dsl.getByType 6 | 7 | class AndroidApplicationComposeConventionPlugin : Plugin { 8 | override fun apply(target: Project) = with(target) { 9 | pluginManager.apply("com.android.application") 10 | val extension = extensions.getByType() 11 | configureAndroidCompose(commonExtension = extension) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | import com.android.build.api.dsl.ApplicationExtension 2 | import dev.mslalith.focuslauncher.configureKotlinAndroid 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.api.artifacts.VersionCatalogsExtension 6 | import org.gradle.kotlin.dsl.configure 7 | import org.gradle.kotlin.dsl.getByType 8 | 9 | class AndroidApplicationConventionPlugin : Plugin { 10 | override fun apply(target: Project) = with(target) { 11 | val libs = extensions.getByType().named("libs") 12 | 13 | with(pluginManager) { 14 | apply("com.android.application") 15 | apply("org.jetbrains.kotlin.android") 16 | apply("focuslauncher.lint") 17 | } 18 | 19 | extensions.configure { 20 | configureKotlinAndroid(commonExtension = this) 21 | defaultConfig.targetSdk = libs.findVersion("androidTargetSdk").get().requiredVersion.toInt() 22 | buildFeatures.buildConfig = true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.LibraryExtension 2 | import dev.mslalith.focuslauncher.configureAndroidCompose 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.kotlin.dsl.getByType 6 | 7 | class AndroidLibraryComposeConventionPlugin : Plugin { 8 | override fun apply(target: Project) { 9 | with(target) { 10 | pluginManager.apply("com.android.library") 11 | val extension = extensions.getByType() 12 | configureAndroidCompose(commonExtension = extension) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.LibraryExtension 2 | import dev.mslalith.focuslauncher.configureKotlinAndroid 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.api.artifacts.VersionCatalogsExtension 6 | import org.gradle.kotlin.dsl.configure 7 | import org.gradle.kotlin.dsl.getByType 8 | 9 | class AndroidLibraryConventionPlugin : Plugin { 10 | override fun apply(target: Project) = with(target) { 11 | val libs = extensions.getByType().named("libs") 12 | 13 | with(pluginManager) { 14 | apply("com.android.library") 15 | apply("focuslauncher.lint") 16 | apply("org.jetbrains.kotlin.android") 17 | apply(libs.findPlugin("kotlinx-kover").get().get().pluginId) 18 | } 19 | 20 | extensions.configure { 21 | configureKotlinAndroid(commonExtension = this) 22 | defaultConfig.targetSdk = libs.findVersion("androidTargetSdk").get().requiredVersion.toInt() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/KotlinLibraryConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | 4 | class KotlinLibraryConventionPlugin : Plugin { 5 | override fun apply(target: Project) = with(target) { 6 | pluginManager.apply("kotlin") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/LintConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Plugin 2 | import org.gradle.api.Project 3 | import org.gradle.api.artifacts.VersionCatalogsExtension 4 | import org.gradle.kotlin.dsl.dependencies 5 | import org.gradle.kotlin.dsl.getByType 6 | import org.gradle.kotlin.dsl.project 7 | 8 | class LintConventionPlugin : Plugin { 9 | override fun apply(target: Project) = with(target) { 10 | pluginManager.apply("io.gitlab.arturbosch.detekt") 11 | 12 | val libs = extensions.getByType().named("libs") 13 | dependencies { 14 | add(configurationName = "implementation", project(":core:lint")) 15 | add(configurationName = "detektPlugins", libs.findLibrary("detekt.formatting").get()) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /build-logic/gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534 2 | org.gradle.parallel=true 3 | org.gradle.caching=true 4 | org.gradle.configureondemand=true 5 | -------------------------------------------------------------------------------- /build-logic/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/build-logic/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /build-logic/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | versionCatalogs { 7 | create("libs") { 8 | from(files("../gradle/libs.versions.toml")) 9 | } 10 | } 11 | } 12 | 13 | rootProject.name = "build-logic" 14 | include(":convention") 15 | -------------------------------------------------------------------------------- /buildScripts/install-git-hooks.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.nio.file.Files 2 | import java.nio.file.Paths 3 | import java.nio.file.attribute.PosixFilePermission 4 | 5 | // Create a task to copy Git hooks from /scripts to .git/hooks path 6 | val installGitHooks by tasks.creating(Copy::class) { 7 | from(layout.projectDirectory.file("scripts/pre-push")) 8 | val toDir = layout.projectDirectory.dir(".git/hooks") 9 | val toFile = toDir.file("pre-push").asFile 10 | val toFilePath = Paths.get(toFile.absolutePath) 11 | into(toDir) 12 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 13 | 14 | doLast { 15 | val perms = Files.getPosixFilePermissions(toFilePath) 16 | perms.add(PosixFilePermission.OWNER_EXECUTE) 17 | Files.setPosixFilePermissions(toFilePath, perms) 18 | } 19 | } 20 | 21 | // Register the Git task to run at beginning 22 | tasks.getByPath(":app:preBuild").dependsOn(installGitHooks) 23 | -------------------------------------------------------------------------------- /core/circuitoverlay/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.library.compose") 4 | alias(libs.plugins.kotlin.parcelize) 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.core.circuitoverlay" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.core.screens) 13 | 14 | implementation(platform(libs.androidx.compose.bom)) 15 | implementation(libs.androidx.compose.material3) 16 | implementation(libs.androidx.compose.ui) 17 | implementation(libs.androidx.compose.ui.tooling) 18 | 19 | implementation(libs.circuit.foundation) 20 | implementation(libs.circuit.runtime) 21 | implementation(libs.circuit.overlay) 22 | } 23 | -------------------------------------------------------------------------------- /core/circuitoverlay/src/main/kotlin/dev/mslalith/focuslauncher/core/circuitoverlay/OverlayResultScreen.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.circuitoverlay 2 | 3 | import com.slack.circuit.runtime.screen.Screen 4 | import kotlinx.parcelize.Parcelize 5 | import kotlinx.parcelize.RawValue 6 | 7 | @Parcelize 8 | data class OverlayResultScreen( 9 | val result: @RawValue T? = null 10 | ) : Screen 11 | -------------------------------------------------------------------------------- /core/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.hilt") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.core.common" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.core.model) 12 | 13 | implementation(platform(libs.androidx.compose.bom)) 14 | implementation(libs.androidx.compose.runtime) 15 | 16 | implementation(libs.kotlinx.datetime) 17 | implementation(libs.kotlinx.coroutines.core) 18 | implementation(libs.kotlinx.collections.immutable) 19 | 20 | testImplementation(libs.truth) 21 | testImplementation(projects.core.testing) 22 | } 23 | -------------------------------------------------------------------------------- /core/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/appcoroutinedispatcher/AppCoroutineDispatcher.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher 2 | 3 | import kotlin.coroutines.CoroutineContext 4 | 5 | interface AppCoroutineDispatcher { 6 | val main: CoroutineContext 7 | val io: CoroutineContext 8 | } 9 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/appcoroutinedispatcher/impl/AppCoroutineDispatcherImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.impl 2 | 3 | import dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.AppCoroutineDispatcher 4 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 5 | import kotlin.coroutines.CoroutineContext 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | @IgnoreInKoverReport 9 | internal class AppCoroutineDispatcherImpl : AppCoroutineDispatcher { 10 | override val main: CoroutineContext = Dispatchers.Main 11 | override val io: CoroutineContext = Dispatchers.IO 12 | } 13 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/appcoroutinedispatcher/test/TestAppCoroutineDispatcher.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.test 2 | 3 | import dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.AppCoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlin.coroutines.CoroutineContext 6 | 7 | class TestAppCoroutineDispatcher( 8 | coroutineContext: CoroutineContext = Dispatchers.Main // this is swapped with TestDispatcher in tests 9 | ) : AppCoroutineDispatcher { 10 | override val main: CoroutineContext = coroutineContext 11 | override val io: CoroutineContext = coroutineContext 12 | } 13 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/extensions/ImmutableExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.extensions 2 | 3 | import kotlinx.collections.immutable.ImmutableList 4 | import kotlinx.collections.immutable.ImmutableMap 5 | import kotlinx.collections.immutable.persistentMapOf 6 | import kotlinx.collections.immutable.toImmutableList 7 | 8 | inline fun Iterable.groupByImmutable( 9 | keySelector: (T) -> K 10 | ): ImmutableMap> { 11 | var persistentMap = persistentMapOf>() 12 | groupBy(keySelector = keySelector).forEach { (key, value) -> 13 | persistentMap = persistentMap.put(key, value.toImmutableList()) 14 | } 15 | return persistentMap 16 | } 17 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/extensions/UtilityExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.extensions 2 | 3 | import java.text.DecimalFormat 4 | import java.text.DecimalFormatSymbols 5 | import java.util.Locale 6 | 7 | 8 | fun Int.to2Digit() = when { 9 | this < 10 -> "0$this" 10 | else -> this 11 | } 12 | 13 | fun Double.asPercent(maxFractions: Int = 2) = limitDecimals(maxFractions = maxFractions) + "%" 14 | 15 | fun Double.limitDecimals( 16 | maxFractions: Int = 2, 17 | minFractions: Int = 2 18 | ): String { 19 | val fractionsPlaceholder = "#".repeat(n = maxFractions) 20 | val decimalFormat = DecimalFormat("#.$fractionsPlaceholder", DecimalFormatSymbols(Locale.US)).apply { 21 | minimumFractionDigits = minFractions 22 | maximumFractionDigits = maxFractions 23 | } 24 | return decimalFormat.format(this) ?: this.toString() 25 | } 26 | 27 | fun Char.isAlphabet() = when (code) { 28 | in 65..90 -> true 29 | in 97..122 -> true 30 | else -> false 31 | } 32 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/model/LoadingState.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | sealed interface LoadingState { 7 | data object Loading : LoadingState 8 | data class Loaded(val value: R) : LoadingState 9 | } 10 | 11 | fun LoadingState.getOrNull(): T? = when (this) { 12 | is LoadingState.Loaded -> this.value 13 | else -> null 14 | } 15 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/model/State.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | sealed class State { 7 | data object Initial : State() 8 | data class Success(val value: R) : State() 9 | } 10 | 11 | fun State.getOrNull(): T? = when (this) { 12 | is State.Success -> this.value 13 | else -> null 14 | } 15 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/network/NetworkMonitor.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.network 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface NetworkMonitor { 6 | val isOnline: Flow 7 | fun isCurrentlyConnected(): Boolean 8 | } 9 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/network/test/FakeNetworkMonitor.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.network.test 2 | 3 | import dev.mslalith.focuslauncher.core.common.network.NetworkMonitor 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.MutableStateFlow 6 | 7 | class FakeNetworkMonitor : NetworkMonitor { 8 | 9 | private val _isOnline = MutableStateFlow(value = true) 10 | override val isOnline: Flow = _isOnline 11 | 12 | override fun isCurrentlyConnected(): Boolean = _isOnline.value 13 | 14 | fun goOnline() { 15 | _isOnline.value = true 16 | } 17 | 18 | fun goOffline() { 19 | _isOnline.value = false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/providers/clock/ClockProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.providers.clock 2 | 3 | import kotlinx.datetime.Instant 4 | 5 | interface ClockProvider { 6 | fun now(): Instant 7 | } 8 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/providers/clock/impl/ClockProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.providers.clock.impl 2 | 3 | import dev.mslalith.focuslauncher.core.common.providers.clock.ClockProvider 4 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 5 | import javax.inject.Inject 6 | import kotlinx.datetime.Clock 7 | import kotlinx.datetime.Instant 8 | 9 | @IgnoreInKoverReport 10 | internal class ClockProviderImpl @Inject constructor() : ClockProvider { 11 | override fun now(): Instant = Clock.System.now() 12 | } 13 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/providers/clock/test/TestClockProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.providers.clock.test 2 | 3 | import dev.mslalith.focuslauncher.core.common.providers.clock.ClockProvider 4 | import javax.inject.Inject 5 | import kotlinx.datetime.Clock 6 | import kotlinx.datetime.Instant 7 | 8 | class TestClockProvider @Inject constructor() : ClockProvider { 9 | 10 | private var instant = Clock.System.now() 11 | 12 | override fun now(): Instant = instant 13 | 14 | fun setInstant(instant: Instant) { 15 | this.instant = instant 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/providers/randomnumber/RandomNumberProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.providers.randomnumber 2 | 3 | interface RandomNumberProvider { 4 | fun random(till: Int): Int 5 | } 6 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/providers/randomnumber/impl/RandomNumberProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.providers.randomnumber.impl 2 | 3 | import dev.mslalith.focuslauncher.core.common.providers.randomnumber.RandomNumberProvider 4 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 5 | import java.util.Random 6 | import javax.inject.Inject 7 | 8 | @IgnoreInKoverReport 9 | internal class RandomNumberProviderImpl @Inject constructor() : RandomNumberProvider { 10 | override fun random(till: Int): Int = Random().nextInt(till) 11 | } 12 | -------------------------------------------------------------------------------- /core/common/src/main/kotlin/dev/mslalith/focuslauncher/core/common/providers/randomnumber/test/TestRandomNumberProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.providers.randomnumber.test 2 | 3 | import dev.mslalith.focuslauncher.core.common.providers.randomnumber.RandomNumberProvider 4 | import javax.inject.Inject 5 | 6 | class TestRandomNumberProvider @Inject constructor() : RandomNumberProvider { 7 | private var randomNumber = 0 8 | 9 | override fun random(till: Int): Int = randomNumber 10 | 11 | fun setRandomNumber(randomNumber: Int) { 12 | this.randomNumber = randomNumber 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/common/src/test/kotlin/dev/mslalith/focuslauncher/core/common/extensions/FormatNumberTest.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.extensions 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import dev.mslalith.focuslauncher.core.testing.extensions.withLocale 5 | import org.junit.Test 6 | import java.util.Locale 7 | 8 | class FormatNumberTest { 9 | 10 | private fun limitDecimalTests() { 11 | assertThat(0.0.limitDecimals(maxFractions = 4)).isEqualTo("0.00") 12 | assertThat(23.0.limitDecimals(maxFractions = 4)).isEqualTo("23.00") 13 | 14 | assertThat(1.23.limitDecimals(maxFractions = 4)).isEqualTo("1.23") 15 | assertThat(1.2345678.limitDecimals(maxFractions = 4)).isEqualTo("1.2346") 16 | 17 | assertThat(753.23.limitDecimals(maxFractions = 4)).isEqualTo("753.23") 18 | assertThat(463.2345678.limitDecimals(maxFractions = 4)).isEqualTo("463.2346") 19 | } 20 | 21 | @Test 22 | fun `limit decimal in US locale`() = withLocale(Locale.US) { limitDecimalTests() } 23 | 24 | @Test 25 | fun `limit decimal in German locale`() = withLocale(Locale.GERMANY) { limitDecimalTests() } 26 | } 27 | -------------------------------------------------------------------------------- /core/common/src/test/kotlin/dev/mslalith/focuslauncher/core/common/extensions/ImmutableExtensionsKtTest.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.common.extensions 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import kotlinx.collections.immutable.persistentListOf 5 | import kotlinx.collections.immutable.persistentMapOf 6 | import org.junit.Test 7 | 8 | class ImmutableExtensionsKtTest { 9 | 10 | @Test 11 | fun `01 - group by first character`() { 12 | val list = listOf("abc", "acc", "bcc", "bcd", "cdc") 13 | val expected = persistentMapOf( 14 | 'a' to persistentListOf("abc", "acc"), 15 | 'b' to persistentListOf("bcc", "bcd"), 16 | 'c' to persistentListOf("cdc") 17 | ) 18 | val actual = list.groupByImmutable { it.first() } 19 | assertThat(actual).isEqualTo(expected) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/data-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | } 4 | 5 | android { 6 | namespace = "dev.mslalith.focuslauncher.core.data.test" 7 | 8 | packaging { 9 | resources.excludes.add("META-INF/*") 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation(projects.core.model) 15 | implementation(projects.core.data) 16 | implementation(projects.core.common) 17 | implementation(projects.core.testing) 18 | 19 | implementation(libs.kotlinx.coroutines.core) 20 | implementation(libs.kotlinx.datetime) 21 | } 22 | -------------------------------------------------------------------------------- /core/data-test/src/main/kotlin/dev/mslalith/focuslauncher/core/data/test/repository/FakeClockRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.test.repository 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.ClockRepo 4 | import kotlinx.coroutines.flow.MutableStateFlow 5 | import kotlinx.coroutines.flow.StateFlow 6 | import kotlinx.coroutines.flow.update 7 | import kotlinx.datetime.Clock 8 | import kotlinx.datetime.Instant 9 | 10 | class FakeClockRepo : ClockRepo { 11 | 12 | private val _currentInstantStateFlow = MutableStateFlow(value = Clock.System.now()) 13 | override val currentInstantStateFlow: StateFlow = _currentInstantStateFlow 14 | 15 | override fun refreshTime() = Unit 16 | 17 | fun setInstant(instant: Instant) { 18 | _currentInstantStateFlow.update { instant } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/data-test/src/main/kotlin/dev/mslalith/focuslauncher/core/data/test/repository/FakePlacesRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.test.repository 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.PlacesRepo 4 | import dev.mslalith.focuslauncher.core.model.location.LatLng 5 | import dev.mslalith.focuslauncher.core.model.place.Place 6 | 7 | class FakePlacesRepo : PlacesRepo { 8 | 9 | private var place: Place? = null 10 | 11 | override suspend fun fetchPlaceLocal(latLng: LatLng): Place? = place 12 | 13 | override suspend fun fetchPlace(latLng: LatLng): Place? = place 14 | 15 | fun setPlace(place: Place?) { 16 | this.place = place 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/data-test/src/main/kotlin/dev/mslalith/focuslauncher/core/data/test/repository/FakeThemeRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.test.repository 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.ThemeRepo 4 | import dev.mslalith.focuslauncher.core.model.Constants.Defaults.Settings.General.DEFAULT_THEME 5 | import dev.mslalith.focuslauncher.core.model.Theme 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import kotlinx.coroutines.flow.update 9 | 10 | class FakeThemeRepo : ThemeRepo { 11 | 12 | private val currentThemeStateFlow = MutableStateFlow(value = DEFAULT_THEME) 13 | override val currentThemeFlow: Flow = currentThemeStateFlow 14 | 15 | override suspend fun changeTheme(theme: Theme) = currentThemeStateFlow.update { theme } 16 | } 17 | -------------------------------------------------------------------------------- /core/data-test/src/main/kotlin/dev/mslalith/focuslauncher/core/data/test/repository/settings/FakeQuotesSettingsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.test.repository.settings 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.settings.QuotesSettingsRepo 4 | import dev.mslalith.focuslauncher.core.model.Constants.Defaults.Settings.Quotes.DEFAULT_SHOW_QUOTES 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.update 8 | 9 | class FakeQuotesSettingsRepo : QuotesSettingsRepo { 10 | 11 | private val showQuotesStateFlow = MutableStateFlow(value = DEFAULT_SHOW_QUOTES) 12 | override val showQuotesFlow: Flow = showQuotesStateFlow 13 | 14 | override suspend fun toggleShowQuotes() { 15 | showQuotesStateFlow.update { !it } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/dao/PlacesDao.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import dev.mslalith.focuslauncher.core.data.database.entities.PlaceRoom 8 | import dev.mslalith.focuslauncher.core.model.Constants.Database.PLACES_TABLE_NAME 9 | 10 | @Dao 11 | internal interface PlacesDao { 12 | 13 | @Query(value = "SELECT * FROM $PLACES_TABLE_NAME WHERE latitude = :latitude AND longitude = :longitude") 14 | suspend fun fetchPlace(latitude: String, longitude: String): PlaceRoom? 15 | 16 | @Insert(onConflict = OnConflictStrategy.REPLACE) 17 | suspend fun insertPlace(place: PlaceRoom) 18 | } 19 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/entities/AppRoom.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | import dev.mslalith.focuslauncher.core.model.Constants.Database.APPS_TABLE_NAME 7 | 8 | @Entity(tableName = APPS_TABLE_NAME) 9 | internal data class AppRoom( 10 | val name: String, 11 | @ColumnInfo(defaultValue = "'name'") 12 | val displayName: String, 13 | @PrimaryKey(autoGenerate = false) 14 | val packageName: String, 15 | val isSystem: Boolean 16 | ) 17 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/entities/FavoriteAppRoom.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import dev.mslalith.focuslauncher.core.model.Constants.Database.FAVORITE_APPS_TABLE_NAME 6 | 7 | @Entity(tableName = FAVORITE_APPS_TABLE_NAME) 8 | internal data class FavoriteAppRoom( 9 | @PrimaryKey(autoGenerate = false) 10 | val packageName: String 11 | ) 12 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/entities/HiddenAppRoom.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import dev.mslalith.focuslauncher.core.model.Constants.Database.HIDDEN_APPS_TABLE_NAME 6 | 7 | @Entity(tableName = HIDDEN_APPS_TABLE_NAME) 8 | internal data class HiddenAppRoom( 9 | @PrimaryKey(autoGenerate = false) 10 | val packageName: String 11 | ) 12 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/entities/QuoteRoom.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.entities 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | import dev.mslalith.focuslauncher.core.model.Constants.Database.QUOTES_TABLE_NAME 6 | 7 | @Entity(tableName = QUOTES_TABLE_NAME) 8 | internal data class QuoteRoom( 9 | @PrimaryKey(autoGenerate = false) 10 | val id: String, 11 | val quote: String, 12 | val author: String, 13 | val authorSlug: String, 14 | val length: Int, 15 | val tags: List 16 | ) 17 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/typeconverter/Converters.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.typeconverter 2 | 3 | import androidx.room.TypeConverter 4 | import kotlinx.serialization.builtins.ListSerializer 5 | import kotlinx.serialization.builtins.serializer 6 | import kotlinx.serialization.json.Json 7 | 8 | internal class Converters { 9 | 10 | @TypeConverter 11 | fun jsonToStringList(json: String): List { 12 | return Json.decodeFromString( 13 | deserializer = ListSerializer(elementSerializer = String.serializer()), 14 | string = json 15 | ) 16 | } 17 | 18 | @TypeConverter 19 | fun stringListToJson(list: List): String { 20 | return Json.encodeToString( 21 | serializer = ListSerializer(elementSerializer = String.serializer()), 22 | value = list 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/usecase/datastore/ClearDataStoreUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.usecase.datastore 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import androidx.datastore.preferences.core.edit 6 | import javax.inject.Inject 7 | 8 | internal class ClearDataStoreUseCase @Inject constructor() { 9 | suspend operator fun invoke(dataStore: DataStore) { 10 | dataStore.edit { it.clear() } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/usecase/datastore/ClearThemeDataStoreUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.usecase.datastore 2 | 3 | import androidx.datastore.core.DataStore 4 | import androidx.datastore.preferences.core.Preferences 5 | import dev.mslalith.focuslauncher.core.data.di.modules.ThemeProvider 6 | import javax.inject.Inject 7 | 8 | class ClearThemeDataStoreUseCase @Inject internal constructor( 9 | private val clearDataStoreUseCase: ClearDataStoreUseCase, 10 | @ThemeProvider private val themeDataStore: DataStore 11 | ) { 12 | suspend operator fun invoke() = clearDataStoreUseCase(dataStore = themeDataStore) 13 | } 14 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/database/usecase/room/CloseDatabaseUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.database.usecase.room 2 | 3 | import dev.mslalith.focuslauncher.core.data.database.AppDatabase 4 | import javax.inject.Inject 5 | 6 | class CloseDatabaseUseCase @Inject internal constructor( 7 | private val appDatabase: AppDatabase 8 | ) { 9 | operator fun invoke() = appDatabase.close() 10 | } 11 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/dto/AddressDto.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.dto 2 | 3 | import dev.mslalith.focuslauncher.core.data.database.entities.AddressRoom 4 | import dev.mslalith.focuslauncher.core.data.network.entities.AddressResponse 5 | import dev.mslalith.focuslauncher.core.model.place.Address 6 | 7 | internal fun AddressResponse.toAddress(): Address = Address( 8 | state = state, 9 | country = country 10 | ) 11 | 12 | internal fun AddressRoom.toAddress(): Address = Address( 13 | state = state, 14 | country = country 15 | ) 16 | 17 | internal fun AddressResponse.toAddressRoom(): AddressRoom = AddressRoom( 18 | houseNumber = houseNumber, 19 | road = road, 20 | village = village, 21 | suburb = suburb, 22 | postCode = postCode, 23 | county = county, 24 | stateDistrict = stateDistrict, 25 | state = state, 26 | iso3166 = iso3166, 27 | country = country, 28 | countryCode = countryCode 29 | ) 30 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/dto/QuoteDto.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.dto 2 | 3 | import dev.mslalith.focuslauncher.core.data.database.entities.QuoteRoom 4 | import dev.mslalith.focuslauncher.core.data.network.entities.QuoteResponse 5 | import dev.mslalith.focuslauncher.core.model.Quote 6 | 7 | internal fun QuoteResponse.toQuoteRoom(): QuoteRoom = QuoteRoom( 8 | id = id, 9 | quote = quote, 10 | author = author, 11 | authorSlug = authorSlug, 12 | length = length, 13 | tags = tags 14 | ) 15 | 16 | internal fun QuoteRoom.toQuote(): Quote = Quote( 17 | id = id, 18 | quote = quote, 19 | author = author 20 | ) 21 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/network/api/PlacesApi.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.network.api 2 | 3 | import dev.mslalith.focuslauncher.core.data.network.entities.PlaceResponse 4 | import dev.mslalith.focuslauncher.core.model.location.LatLng 5 | 6 | internal interface PlacesApi { 7 | suspend fun getPlace(latLng: LatLng): Result 8 | } 9 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/network/api/QuotesApi.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.network.api 2 | 3 | import dev.mslalith.focuslauncher.core.data.network.entities.QuotesApiResponse 4 | import dev.mslalith.focuslauncher.core.model.Constants.Defaults.QUOTES_LIMIT_PER_PAGE 5 | 6 | internal interface QuotesApi { 7 | suspend fun getQuotes( 8 | page: Int = 1, 9 | limit: Int = QUOTES_LIMIT_PER_PAGE 10 | ): Result 11 | } 12 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/network/api/fakes/FakePlacesApi.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.network.api.fakes 2 | 3 | import dev.mslalith.focuslauncher.core.data.network.api.PlacesApi 4 | import dev.mslalith.focuslauncher.core.data.network.entities.PlaceResponse 5 | import dev.mslalith.focuslauncher.core.data.utils.dummyPlaceResponseFor 6 | import dev.mslalith.focuslauncher.core.model.location.LatLng 7 | 8 | internal class FakePlacesApi : PlacesApi { 9 | override suspend fun getPlace(latLng: LatLng): Result = Result.success( 10 | value = dummyPlaceResponseFor(latLng = latLng) 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/network/api/fakes/FakeQuotesApi.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.network.api.fakes 2 | 3 | import dev.mslalith.focuslauncher.core.data.network.api.QuotesApi 4 | import dev.mslalith.focuslauncher.core.data.network.entities.QuotesApiResponse 5 | import dev.mslalith.focuslauncher.core.data.utils.dummyQuoteResponseFor 6 | 7 | internal class FakeQuotesApi : QuotesApi { 8 | override suspend fun getQuotes(page: Int, limit: Int): Result { 9 | val pageOffset = (page - 1) * limit 10 | return Result.success( 11 | value = QuotesApiResponse( 12 | count = limit, 13 | totalCount = 100, 14 | page = page, 15 | totalPages = 20, 16 | lastItemIndex = 9, 17 | results = List(size = limit) { dummyQuoteResponseFor(index = pageOffset + it) } 18 | ) 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/network/api/impl/PlacesApiImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.network.api.impl 2 | 3 | import dev.mslalith.focuslauncher.core.data.network.api.PlacesApi 4 | import dev.mslalith.focuslauncher.core.data.network.entities.PlaceResponse 5 | import dev.mslalith.focuslauncher.core.model.location.LatLng 6 | import io.ktor.client.HttpClient 7 | import io.ktor.client.call.body 8 | import io.ktor.client.request.get 9 | import io.ktor.client.request.parameter 10 | import javax.inject.Inject 11 | 12 | internal class PlacesApiImpl @Inject constructor( 13 | private val httpClient: HttpClient 14 | ) : PlacesApi { 15 | 16 | override suspend fun getPlace(latLng: LatLng): Result = runCatching { 17 | httpClient.get(urlString = "https://nominatim.openstreetmap.org/reverse") { 18 | parameter(key = "format", value = "json") 19 | parameter(key = "lat", value = latLng.latitude) 20 | parameter(key = "lon", value = latLng.longitude) 21 | }.body() 22 | }.recover { PlaceResponse.default() } 23 | } 24 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/AppDrawerRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.model.app.App 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface AppDrawerRepo { 7 | val allAppsFlow: Flow> 8 | 9 | suspend fun getAppBy(packageName: String): App? 10 | suspend fun addApps(apps: List) 11 | suspend fun addApp(app: App) 12 | suspend fun removeApp(app: App) 13 | suspend fun clearApps() 14 | suspend fun updateDisplayName(app: App, displayName: String) 15 | suspend fun areAppsEmptyInDatabase(): Boolean 16 | } 17 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/ClockRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import kotlinx.coroutines.flow.StateFlow 4 | import kotlinx.datetime.Instant 5 | 6 | interface ClockRepo { 7 | val currentInstantStateFlow: StateFlow 8 | fun refreshTime() 9 | } 10 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/FavoritesRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.model.app.App 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface FavoritesRepo { 7 | val onlyFavoritesFlow: Flow> 8 | 9 | suspend fun addToFavorites(app: App) 10 | suspend fun addToFavorites(apps: List) 11 | suspend fun reorderFavorite(app: App, withApp: App) 12 | suspend fun removeFromFavorites(packageName: String) 13 | suspend fun clearFavorites() 14 | suspend fun isFavorite(packageName: String): Boolean 15 | } 16 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/HiddenAppsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.model.app.App 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface HiddenAppsRepo { 7 | val onlyHiddenAppsFlow: Flow> 8 | 9 | suspend fun addToHiddenApps(app: App) 10 | suspend fun addToHiddenApps(apps: List) 11 | suspend fun removeFromHiddenApps(packageName: String) 12 | suspend fun clearHiddenApps() 13 | suspend fun isHidden(packageName: String): Boolean 14 | } 15 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/LunarPhaseDetailsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.common.model.State 4 | import dev.mslalith.focuslauncher.core.model.location.LatLng 5 | import dev.mslalith.focuslauncher.core.model.lunarphase.LunarPhaseDetails 6 | import dev.mslalith.focuslauncher.core.model.lunarphase.UpcomingLunarPhase 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.datetime.Instant 9 | 10 | interface LunarPhaseDetailsRepo { 11 | val lunarPhaseDetailsStateFlow: StateFlow> 12 | val upcomingLunarPhaseStateFlow: StateFlow> 13 | 14 | suspend fun refreshLunarPhaseDetails(instant: Instant, latLng: LatLng) 15 | } 16 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/PlacesRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.model.location.LatLng 4 | import dev.mslalith.focuslauncher.core.model.place.Place 5 | 6 | interface PlacesRepo { 7 | suspend fun fetchPlaceLocal(latLng: LatLng): Place? 8 | suspend fun fetchPlace(latLng: LatLng): Place? 9 | } 10 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/QuotesRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.common.model.State 4 | import dev.mslalith.focuslauncher.core.model.Quote 5 | import kotlinx.coroutines.flow.StateFlow 6 | 7 | interface QuotesRepo { 8 | val currentQuoteStateFlow: StateFlow> 9 | val isFetchingQuotesStateFlow: StateFlow 10 | 11 | suspend fun nextRandomQuote() 12 | suspend fun fetchQuotes(maxPages: Int) 13 | suspend fun addInitialQuotesIfNeeded() 14 | suspend fun hasQuotesReachedLimit(): Boolean 15 | suspend fun quotesSize(): Int 16 | } 17 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/ThemeRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository 2 | 3 | import dev.mslalith.focuslauncher.core.model.Theme 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface ThemeRepo { 7 | val currentThemeFlow: Flow 8 | 9 | suspend fun changeTheme(theme: Theme) 10 | } 11 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/impl/ClockRepoImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository.impl 2 | 3 | import dev.mslalith.focuslauncher.core.common.providers.clock.ClockProvider 4 | import dev.mslalith.focuslauncher.core.data.repository.ClockRepo 5 | import javax.inject.Inject 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.StateFlow 8 | import kotlinx.datetime.Instant 9 | 10 | internal class ClockRepoImpl @Inject constructor( 11 | private val clockProvider: ClockProvider 12 | ) : ClockRepo { 13 | 14 | private val _currentInstantStateFlow = MutableStateFlow(value = clockProvider.now()) 15 | override val currentInstantStateFlow: StateFlow 16 | get() = _currentInstantStateFlow 17 | 18 | init { refreshTime() } 19 | 20 | override fun refreshTime() { 21 | _currentInstantStateFlow.value = clockProvider.now() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/settings/AppDrawerSettingsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository.settings 2 | 3 | import dev.mslalith.focuslauncher.core.model.AppDrawerViewType 4 | import dev.mslalith.focuslauncher.core.model.appdrawer.AppDrawerIconViewType 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface AppDrawerSettingsRepo { 8 | val appDrawerViewTypeFlow: Flow 9 | val appIconsVisibilityFlow: Flow 10 | val appDrawerIconViewTypeFlow: Flow 11 | val searchBarVisibilityFlow: Flow 12 | val appGroupHeaderVisibilityFlow: Flow 13 | 14 | suspend fun updateAppDrawerViewType(appDrawerViewType: AppDrawerViewType) 15 | suspend fun updateAppDrawerIconViewType(appDrawerIconViewType: AppDrawerIconViewType) 16 | suspend fun toggleAppIconsVisibility() 17 | suspend fun toggleSearchBarVisibility() 18 | suspend fun toggleAppGroupHeaderVisibility() 19 | } 20 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/settings/ClockSettingsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository.settings 2 | 3 | import dev.mslalith.focuslauncher.core.model.ClockAlignment 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface ClockSettingsRepo { 7 | val showClock24Flow: Flow 8 | val use24HourFlow: Flow 9 | val clockAlignmentFlow: Flow 10 | val clock24AnimationDurationFlow: Flow 11 | 12 | suspend fun toggleClock24() 13 | suspend fun toggleUse24Hour() 14 | suspend fun updateClockAlignment(clockAlignment: ClockAlignment) 15 | suspend fun updateClock24AnimationDuration(duration: Int) 16 | } 17 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/settings/GeneralSettingsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository.settings 2 | 3 | import dev.mslalith.focuslauncher.core.model.IconPackType 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface GeneralSettingsRepo { 7 | val firstRunFlow: Flow 8 | val statusBarVisibilityFlow: Flow 9 | val notificationShadeFlow: Flow 10 | val isDefaultLauncher: Flow 11 | val iconPackTypeFlow: Flow 12 | val reportCrashesFlow: Flow 13 | 14 | suspend fun overrideFirstRun() 15 | suspend fun toggleStatusBarVisibility() 16 | suspend fun toggleNotificationShade() 17 | suspend fun setIsDefaultLauncher(isDefault: Boolean) 18 | suspend fun updateIconPackType(iconPackType: IconPackType) 19 | suspend fun updateReportCrashes(enabled: Boolean) 20 | } 21 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/settings/LunarPhaseSettingsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository.settings 2 | 3 | import dev.mslalith.focuslauncher.core.model.CurrentPlace 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface LunarPhaseSettingsRepo { 7 | val showLunarPhaseFlow: Flow 8 | val showIlluminationPercentFlow: Flow 9 | val showUpcomingPhaseDetailsFlow: Flow 10 | val currentPlaceFlow: Flow 11 | 12 | suspend fun toggleShowLunarPhase() 13 | suspend fun toggleShowIlluminationPercent() 14 | suspend fun toggleShowUpcomingPhaseDetails() 15 | suspend fun updateCurrentPlace(currentPlace: CurrentPlace) 16 | } 17 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/repository/settings/QuotesSettingsRepo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.repository.settings 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface QuotesSettingsRepo { 6 | val showQuotesFlow: Flow 7 | 8 | suspend fun toggleShowQuotes() 9 | } 10 | -------------------------------------------------------------------------------- /core/data/src/main/kotlin/dev/mslalith/focuslauncher/core/data/utils/QuotesRepoHelpers.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.utils 2 | 3 | import dev.mslalith.focuslauncher.core.data.network.entities.QuoteResponse 4 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 5 | import dev.mslalith.focuslauncher.core.model.Quote 6 | 7 | @IgnoreInKoverReport 8 | internal fun dummyQuoteResponseFor(index: Int) = QuoteResponse( 9 | id = "ID #$index", 10 | quote = "Quote #$index", 11 | author = "Author #$index", 12 | authorSlug = "Author Slug #$index", 13 | tags = listOf(), 14 | length = 20 15 | ) 16 | 17 | @IgnoreInKoverReport 18 | fun dummyQuoteFor(index: Int) = Quote( 19 | id = "ID #$index", 20 | quote = "Quote #$index", 21 | author = "Author #$index" 22 | ) 23 | -------------------------------------------------------------------------------- /core/data/src/test/kotlin/dev/mslalith/focuslauncher/core/data/helpers/SettingsRepoHelpers.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.data.helpers 2 | 3 | import app.cash.turbine.test 4 | import com.google.common.truth.Truth.assertThat 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | suspend fun verifyChange( 8 | flow: Flow, 9 | initialItem: T, 10 | mutate: suspend () -> T 11 | ) = flow.test { 12 | assertThat(awaitItem()).isEqualTo(initialItem) 13 | val newItem = mutate() 14 | assertThat(awaitItem()).isEqualTo(newItem) 15 | } 16 | -------------------------------------------------------------------------------- /core/domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.hilt") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.core.domain" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.core.common) 12 | implementation(projects.core.data) 13 | implementation(projects.core.model) 14 | implementation(projects.core.launcherapps) 15 | implementation(projects.core.settings.sentry) 16 | 17 | implementation(libs.androidx.palette.ktx) 18 | 19 | testImplementation(projects.core.testing) 20 | } 21 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/apps/GetFavoriteColoredAppsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.apps 2 | 3 | import dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.AppCoroutineDispatcher 4 | import dev.mslalith.focuslauncher.core.data.repository.FavoritesRepo 5 | import dev.mslalith.focuslauncher.core.domain.apps.core.GetAppsIconPackAwareUseCase 6 | import dev.mslalith.focuslauncher.core.model.app.AppWithColor 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flowOn 9 | import javax.inject.Inject 10 | 11 | class GetFavoriteColoredAppsUseCase @Inject internal constructor( 12 | private val getAppsIconPackAwareUseCase: GetAppsIconPackAwareUseCase, 13 | private val favoritesRepo: FavoritesRepo, 14 | private val appCoroutineDispatcher: AppCoroutineDispatcher 15 | ) { 16 | operator fun invoke(): Flow> = getAppsIconPackAwareUseCase.appsWithColor( 17 | appsFlow = favoritesRepo.onlyFavoritesFlow 18 | ).flowOn(context = appCoroutineDispatcher.io) 19 | } 20 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/apps/GetIconPackIconicAppsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.apps 2 | 3 | import dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.AppCoroutineDispatcher 4 | import dev.mslalith.focuslauncher.core.domain.apps.core.GetAppsIconPackAwareUseCase 5 | import dev.mslalith.focuslauncher.core.domain.iconpack.GetIconPackAppsUseCase 6 | import dev.mslalith.focuslauncher.core.model.app.AppWithIcon 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flowOn 9 | import javax.inject.Inject 10 | 11 | class GetIconPackIconicAppsUseCase @Inject internal constructor( 12 | private val getAppsIconPackAwareUseCase: GetAppsIconPackAwareUseCase, 13 | private val getIconPackAppsUseCase: GetIconPackAppsUseCase, 14 | private val appCoroutineDispatcher: AppCoroutineDispatcher 15 | ) { 16 | operator fun invoke(): Flow> = getAppsIconPackAwareUseCase.appsWithIcons( 17 | appsFlow = getIconPackAppsUseCase() 18 | ).flowOn(context = appCoroutineDispatcher.io) 19 | } 20 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/iconpack/FetchIconPacksUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.iconpack 2 | 3 | import dev.mslalith.focuslauncher.core.launcherapps.manager.iconpack.IconPackManager 4 | import javax.inject.Inject 5 | 6 | class FetchIconPacksUseCase @Inject constructor( 7 | private val iconPackManager: IconPackManager 8 | ) { 9 | operator fun invoke() = iconPackManager.fetchInstalledIconPacks() 10 | } 11 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/iconpack/GetIconPackAppsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.iconpack 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.AppDrawerRepo 4 | import dev.mslalith.focuslauncher.core.launcherapps.manager.iconpack.IconPackManager 5 | import dev.mslalith.focuslauncher.core.model.app.App 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.combine 8 | import javax.inject.Inject 9 | 10 | class GetIconPackAppsUseCase @Inject constructor( 11 | private val iconPackManager: IconPackManager, 12 | private val appDrawerRepo: AppDrawerRepo 13 | ) { 14 | operator fun invoke(): Flow> = appDrawerRepo.allAppsFlow 15 | .combine(flow = iconPackManager.iconPacksFlow) { allApps, iconPacks -> 16 | allApps.filter { app -> 17 | iconPacks.any { it.packageName == app.packageName } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/iconpack/LoadIconPackUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.iconpack 2 | 3 | import dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.AppCoroutineDispatcher 4 | import dev.mslalith.focuslauncher.core.launcherapps.manager.iconpack.IconPackManager 5 | import dev.mslalith.focuslauncher.core.model.IconPackType 6 | import kotlinx.coroutines.withContext 7 | import javax.inject.Inject 8 | 9 | class LoadIconPackUseCase @Inject constructor( 10 | private val iconPackManager: IconPackManager, 11 | private val appCoroutineDispatcher: AppCoroutineDispatcher 12 | ) { 13 | suspend operator fun invoke(iconPackType: IconPackType) = withContext(appCoroutineDispatcher.io) { 14 | iconPackManager.loadIconPack( 15 | iconPackType = iconPackType 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/iconpack/ReloadIconPackUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.iconpack 2 | 3 | import dev.mslalith.focuslauncher.core.common.appcoroutinedispatcher.AppCoroutineDispatcher 4 | import dev.mslalith.focuslauncher.core.launcherapps.manager.iconpack.IconPackManager 5 | import kotlinx.coroutines.withContext 6 | import javax.inject.Inject 7 | 8 | class ReloadIconPackUseCase @Inject constructor( 9 | private val iconPackManager: IconPackManager, 10 | private val appCoroutineDispatcher: AppCoroutineDispatcher 11 | ) { 12 | suspend operator fun invoke() = withContext(appCoroutineDispatcher.io) { 13 | iconPackManager.reloadIconPack() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/launcherapps/GetDefaultFavoriteAppsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.launcherapps 2 | 3 | import dev.mslalith.focuslauncher.core.launcherapps.manager.launcherapps.LauncherAppsManager 4 | import dev.mslalith.focuslauncher.core.model.app.App 5 | import javax.inject.Inject 6 | 7 | class GetDefaultFavoriteAppsUseCase @Inject constructor( 8 | private val launcherAppsManager: LauncherAppsManager 9 | ) { 10 | suspend operator fun invoke(): List = launcherAppsManager.defaultFavoriteApps().map { it.app } 11 | } 12 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/launcherapps/LoadAllAppsUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.launcherapps 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.AppDrawerRepo 4 | import dev.mslalith.focuslauncher.core.launcherapps.manager.launcherapps.LauncherAppsManager 5 | import javax.inject.Inject 6 | 7 | class LoadAllAppsUseCase @Inject constructor( 8 | private val launcherAppsManager: LauncherAppsManager, 9 | private val appDrawerRepo: AppDrawerRepo 10 | ) { 11 | suspend operator fun invoke(forceLoad: Boolean = false) { 12 | appDrawerRepo.apply { 13 | if (!forceLoad && !areAppsEmptyInDatabase()) return 14 | addApps(apps = launcherAppsManager.loadAllApps().map { it.app }) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/settings/UpdateReportCrashesSettingUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.settings 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.settings.GeneralSettingsRepo 4 | import dev.mslalith.focuslauncher.core.settings.sentry.SentrySettings 5 | import javax.inject.Inject 6 | 7 | class UpdateReportCrashesSettingUseCase @Inject constructor( 8 | private val sentrySettings: SentrySettings, 9 | private val generalSettingsRepo: GeneralSettingsRepo 10 | ) { 11 | suspend operator fun invoke(enabled: Boolean) { 12 | generalSettingsRepo.updateReportCrashes(enabled = enabled) 13 | with(sentrySettings) { 14 | if (enabled) enableSentry() else disableSentry() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/theme/ChangeThemeUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.theme 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.ThemeRepo 4 | import dev.mslalith.focuslauncher.core.model.Theme 5 | import javax.inject.Inject 6 | 7 | class ChangeThemeUseCase @Inject constructor( 8 | private val themeRepo: ThemeRepo 9 | ) { 10 | suspend operator fun invoke(theme: Theme) = themeRepo.changeTheme(theme = theme) 11 | } 12 | -------------------------------------------------------------------------------- /core/domain/src/main/kotlin/dev/mslalith/focuslauncher/core/domain/theme/GetThemeUseCase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.theme 2 | 3 | import dev.mslalith.focuslauncher.core.data.repository.ThemeRepo 4 | import dev.mslalith.focuslauncher.core.model.Theme 5 | import kotlinx.coroutines.flow.Flow 6 | import javax.inject.Inject 7 | 8 | class GetThemeUseCase @Inject constructor( 9 | private val themeRepo: ThemeRepo 10 | ) { 11 | operator fun invoke(): Flow = themeRepo.currentThemeFlow 12 | } 13 | -------------------------------------------------------------------------------- /core/domain/src/test/kotlin/dev/mslalith/focuslauncher/core/domain/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.domain.utils 2 | 3 | import dev.mslalith.focuslauncher.core.launcherapps.model.IconPack 4 | import dev.mslalith.focuslauncher.core.model.app.App 5 | 6 | internal fun List.toIconPacks(): List = map(App::toIconPack) 7 | 8 | internal fun App.toIconPack(): IconPack = IconPack( 9 | label = name, 10 | packageName = packageName 11 | ) 12 | -------------------------------------------------------------------------------- /core/launcherapps/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/launcherapps/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.hilt") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.core.launcherapps" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.core.model) 12 | implementation(projects.core.data) 13 | 14 | testImplementation(projects.core.testing) 15 | } 16 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/manager/iconcache/IconCacheManager.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.manager.iconcache 2 | 3 | import android.graphics.drawable.Drawable 4 | import dev.mslalith.focuslauncher.core.launcherapps.parser.IconPackXmlParser 5 | import dev.mslalith.focuslauncher.core.model.IconPackType 6 | import dev.mslalith.focuslauncher.core.model.app.AppWithComponent 7 | 8 | internal interface IconCacheManager { 9 | fun clearCache() 10 | fun iconPackFor(packageName: String): IconPackXmlParser 11 | fun iconFor(appWithComponent: AppWithComponent, iconPackType: IconPackType): Drawable 12 | } 13 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/manager/iconpack/IconPackManager.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.manager.iconpack 2 | 3 | import dev.mslalith.focuslauncher.core.launcherapps.model.IconPack 4 | import dev.mslalith.focuslauncher.core.model.IconPackLoadEvent 5 | import dev.mslalith.focuslauncher.core.model.IconPackType 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface IconPackManager { 9 | val iconPacksFlow: Flow> 10 | val iconPackLoadEventFlow: Flow 11 | 12 | fun fetchInstalledIconPacks() 13 | suspend fun loadIconPack(iconPackType: IconPackType) 14 | suspend fun reloadIconPack() 15 | } 16 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/manager/launcherapps/LauncherAppsManager.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.manager.launcherapps 2 | 3 | import dev.mslalith.focuslauncher.core.model.app.AppWithComponent 4 | 5 | interface LauncherAppsManager { 6 | suspend fun loadAllApps(): List 7 | suspend fun loadApp(packageName: String): AppWithComponent? 8 | suspend fun defaultFavoriteApps(): List 9 | } 10 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/model/DrawableInfo.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.model 2 | 3 | import androidx.annotation.DrawableRes 4 | import java.util.Calendar 5 | 6 | internal sealed class DrawableInfo( 7 | open val drawableName: String 8 | ) { 9 | @DrawableRes 10 | abstract fun getDrawableResId(): Int 11 | } 12 | 13 | internal class SimpleDrawableInfo( 14 | override val drawableName: String, 15 | @DrawableRes val drawableId: Int 16 | ) : DrawableInfo(drawableName = drawableName) { 17 | override fun getDrawableResId(): Int = drawableId 18 | } 19 | 20 | internal class CalendarDrawableInfo( 21 | override val drawableName: String 22 | ) : DrawableInfo(drawableName = drawableName) { 23 | 24 | private val drawableForDay = IntArray(size = 31) 25 | 26 | fun setDrawableForDay(day: Int, @DrawableRes drawableId: Int) { 27 | drawableForDay[day] = drawableId 28 | } 29 | 30 | override fun getDrawableResId(): Int = drawableForDay[Calendar.getInstance()[Calendar.DAY_OF_MONTH] - 1] 31 | } 32 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/model/IconPack.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.model 2 | 3 | data class IconPack( 4 | val label: String, 5 | val packageName: String 6 | ) 7 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/providers/icons/IconProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.providers.icons 2 | 3 | import android.graphics.drawable.Drawable 4 | import dev.mslalith.focuslauncher.core.model.IconPackType 5 | import dev.mslalith.focuslauncher.core.model.app.AppWithComponent 6 | 7 | interface IconProvider { 8 | fun iconFor(appWithComponent: AppWithComponent, iconPackType: IconPackType): Drawable 9 | } 10 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/providers/icons/impl/IconProviderImpl.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.providers.icons.impl 2 | 3 | import android.graphics.drawable.Drawable 4 | import dev.mslalith.focuslauncher.core.launcherapps.manager.iconcache.IconCacheManager 5 | import dev.mslalith.focuslauncher.core.launcherapps.providers.icons.IconProvider 6 | import dev.mslalith.focuslauncher.core.model.IconPackType 7 | import dev.mslalith.focuslauncher.core.model.app.AppWithComponent 8 | import javax.inject.Inject 9 | 10 | internal class IconProviderImpl @Inject constructor( 11 | private val iconCacheManager: IconCacheManager 12 | ) : IconProvider { 13 | 14 | override fun iconFor(appWithComponent: AppWithComponent, iconPackType: IconPackType): Drawable = iconCacheManager.iconFor( 15 | appWithComponent = appWithComponent, 16 | iconPackType = iconPackType 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /core/launcherapps/src/main/kotlin/dev/mslalith/focuslauncher/core/launcherapps/providers/icons/test/TestIconProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.launcherapps.providers.icons.test 2 | 3 | import android.graphics.Color 4 | import android.graphics.drawable.ColorDrawable 5 | import android.graphics.drawable.Drawable 6 | import dev.mslalith.focuslauncher.core.launcherapps.providers.icons.IconProvider 7 | import dev.mslalith.focuslauncher.core.model.IconPackType 8 | import dev.mslalith.focuslauncher.core.model.app.AppWithComponent 9 | 10 | object TestIconProvider : IconProvider { 11 | 12 | private var drawable = ColorDrawable(Color.WHITE) 13 | 14 | fun setIconColor(color: Int) { 15 | drawable = ColorDrawable(color) 16 | } 17 | 18 | override fun iconFor(appWithComponent: AppWithComponent, iconPackType: IconPackType): Drawable = drawable 19 | } 20 | -------------------------------------------------------------------------------- /core/lint/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/lint/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.kotlin.library") 3 | } 4 | 5 | dependencies { 6 | detektPlugins(libs.detekt.formatting) 7 | } 8 | -------------------------------------------------------------------------------- /core/lint/src/main/kotlin/dev/mslalith/focuslauncher/core/lint/detekt/IgnoreCyclomaticComplexMethod.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.lint.detekt 2 | 3 | @Retention(value = AnnotationRetention.BINARY) 4 | @Target(AnnotationTarget.FUNCTION) 5 | annotation class IgnoreCyclomaticComplexMethod 6 | -------------------------------------------------------------------------------- /core/lint/src/main/kotlin/dev/mslalith/focuslauncher/core/lint/detekt/IgnoreLongMethod.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.lint.detekt 2 | 3 | @Retention(value = AnnotationRetention.BINARY) 4 | @Target(AnnotationTarget.FUNCTION) 5 | annotation class IgnoreLongMethod 6 | -------------------------------------------------------------------------------- /core/lint/src/main/kotlin/dev/mslalith/focuslauncher/core/lint/detekt/IgnoreNestedBlockDepth.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.lint.detekt 2 | 3 | @Retention(value = AnnotationRetention.BINARY) 4 | @Target(AnnotationTarget.FUNCTION) 5 | annotation class IgnoreNestedBlockDepth 6 | -------------------------------------------------------------------------------- /core/lint/src/main/kotlin/dev/mslalith/focuslauncher/core/lint/kover/IgnoreInKoverReport.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.lint.kover 2 | 3 | @Retention(value = AnnotationRetention.BINARY) 4 | @Target( 5 | AnnotationTarget.FILE, 6 | AnnotationTarget.CLASS, 7 | AnnotationTarget.FUNCTION, 8 | AnnotationTarget.PROPERTY 9 | ) 10 | annotation class IgnoreInKoverReport 11 | -------------------------------------------------------------------------------- /core/model/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/model/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.lint") 4 | alias(libs.plugins.kotlin.serialization) 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.core.model" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.core.resources) 13 | 14 | implementation(platform(libs.androidx.compose.bom)) 15 | implementation(libs.androidx.compose.runtime) 16 | 17 | implementation(libs.kotlinx.datetime) 18 | implementation(libs.kotlinx.serialization) 19 | implementation(libs.androidx.annotation) 20 | implementation(libs.suncalc) 21 | } 22 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/AppDrawerViewType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | enum class AppDrawerViewType( 4 | val index: Int, 5 | val uiText: UiText 6 | ) { 7 | LIST( 8 | index = 0, 9 | uiText = UiText.Resource(stringRes = R.string.list) 10 | ), 11 | GRID( 12 | index = 1, 13 | uiText = UiText.Resource(stringRes = R.string.grid) 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/BuildFlavor.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | var BUILD_FLAVOR: BuildFlavor = BuildFlavor.Store 4 | 5 | enum class BuildFlavor(val id: String) { 6 | Dev(id = "dev"), 7 | Store(id = "store"); 8 | 9 | companion object { 10 | fun fromId(id: String) = entries.firstOrNull { it.id == id } ?: Store 11 | } 12 | 13 | fun isDev(): Boolean = this == Dev 14 | } 15 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/ClockAlignment.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | enum class ClockAlignment( 4 | val index: Int, 5 | val uiText: UiText 6 | ) { 7 | START( 8 | index = 0, 9 | uiText = UiText.Resource(stringRes = R.string.start) 10 | ), 11 | CENTER( 12 | index = 1, 13 | uiText = UiText.Resource(stringRes = R.string.center) 14 | ), 15 | END( 16 | index = 2, 17 | uiText = UiText.Resource(stringRes = R.string.end) 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/ConfirmSelectableItemType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.runtime.Immutable 5 | 6 | @Immutable 7 | sealed class ConfirmSelectableItemType { 8 | data class Icon( 9 | @DrawableRes val iconRes: Int, 10 | val contentDescription: String? = null 11 | ) : ConfirmSelectableItemType() 12 | 13 | data class Checkbox( 14 | val checked: Boolean, 15 | val disabled: Boolean = false 16 | ) : ConfirmSelectableItemType() 17 | } 18 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/CurrentPlace.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | import dev.mslalith.focuslauncher.core.model.location.LatLng 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class CurrentPlace( 8 | val latLng: LatLng, 9 | val address: String 10 | ) 11 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/IconPackLoadEvent.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | sealed class IconPackLoadEvent(open val iconPackType: IconPackType) { 4 | data class Loading(override val iconPackType: IconPackType) : IconPackLoadEvent(iconPackType = iconPackType) 5 | data class Loaded(override val iconPackType: IconPackType) : IconPackLoadEvent(iconPackType = iconPackType) 6 | data class Reloading(override val iconPackType: IconPackType) : IconPackLoadEvent(iconPackType = iconPackType) 7 | data class Reloaded(override val iconPackType: IconPackType) : IconPackLoadEvent(iconPackType = iconPackType) 8 | } 9 | 10 | val IconPackLoadEvent.isTerminal: Boolean 11 | get() = when (this) { 12 | is IconPackLoadEvent.Loaded, is IconPackLoadEvent.Reloaded -> true 13 | else -> false 14 | } 15 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/IconPackType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | sealed interface IconPackType { 4 | data object System : IconPackType 5 | data class Custom(val packageName: String) : IconPackType 6 | 7 | val value: String 8 | get() = when (this) { 9 | is Custom -> packageName 10 | System -> "default" 11 | } 12 | 13 | companion object { 14 | fun isSystemPack(value: String): Boolean = value == "default" 15 | 16 | fun from(value: String): IconPackType = when (value) { 17 | "default" -> System 18 | else -> Custom(packageName = value) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/PackageAction.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | sealed interface PackageAction { 4 | data class Added(val packageName: String) : PackageAction 5 | data class Updated(val packageName: String) : PackageAction 6 | data class Removed(val packageName: String) : PackageAction 7 | } 8 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/Quote.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | data class Quote( 7 | val id: String, 8 | val quote: String, 9 | val author: String 10 | ) 11 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/Theme.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | enum class Theme(val uiText: UiText) { 4 | FOLLOW_SYSTEM(uiText = UiText.Resource(stringRes = R.string.follow_system)), 5 | NOT_WHITE(uiText = UiText.Resource(stringRes = R.string.not_white)), 6 | SAID_DARK(uiText = UiText.Resource(stringRes = R.string.said_dark)) 7 | } 8 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/UiText.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | import android.content.Context 4 | import androidx.annotation.StringRes 5 | import androidx.compose.runtime.Immutable 6 | 7 | @Immutable 8 | sealed interface UiText { 9 | 10 | data class Static(val text: String) : UiText 11 | data class Resource(@StringRes val stringRes: Int) : UiText 12 | 13 | fun string(context: Context): String = when (this) { 14 | is Static -> text 15 | is Resource -> context.getString(stringRes) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/WidgetType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model 2 | 3 | enum class WidgetType(val uiText: UiText) { 4 | CLOCK(uiText = UiText.Resource(stringRes = R.string.clock)), 5 | LUNAR_PHASE(uiText = UiText.Resource(stringRes = R.string.lunar_phase)), 6 | QUOTES(uiText = UiText.Resource(stringRes = R.string.quotes)) 7 | } 8 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/app/App.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.app 2 | 3 | import androidx.compose.runtime.Immutable 4 | import kotlinx.serialization.Serializable 5 | 6 | @Immutable 7 | @Serializable 8 | data class App( 9 | val name: String, 10 | val displayName: String = name, 11 | val packageName: String, 12 | val isSystem: Boolean 13 | ) 14 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/app/AppWithColor.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.app 2 | 3 | import android.graphics.Color 4 | import androidx.compose.runtime.Immutable 5 | 6 | @Immutable 7 | data class AppWithColor( 8 | val app: App, 9 | val color: Color? 10 | ) 11 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/app/AppWithComponent.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.app 2 | 3 | import android.content.ComponentName 4 | 5 | data class AppWithComponent( 6 | val app: App, 7 | val componentName: ComponentName 8 | ) 9 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/app/AppWithIcon.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.app 2 | 3 | import android.graphics.drawable.Drawable 4 | import androidx.compose.runtime.Immutable 5 | import dev.mslalith.focuslauncher.core.model.extensions.generateHashCode 6 | 7 | @Immutable 8 | data class AppWithIcon( 9 | val app: App, 10 | val icon: Drawable 11 | ) { 12 | val uniqueKey: Int 13 | get() = listOf(app.packageName, icon).generateHashCode() 14 | } 15 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/app/SelectedApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.app 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | data class SelectedApp( 7 | val app: App, 8 | val isSelected: Boolean, 9 | val disabled: Boolean = false 10 | ) 11 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/app/SelectedHiddenApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.app 2 | 3 | import androidx.compose.runtime.Immutable 4 | 5 | @Immutable 6 | data class SelectedHiddenApp( 7 | val app: App, 8 | val isSelected: Boolean, 9 | val isFavorite: Boolean 10 | ) 11 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/appdrawer/AppDrawerIconViewType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.appdrawer 2 | 3 | import dev.mslalith.focuslauncher.core.model.R 4 | import dev.mslalith.focuslauncher.core.model.UiText 5 | 6 | enum class AppDrawerIconViewType( 7 | val index: Int, 8 | val uiText: UiText 9 | ) { 10 | TEXT(index = 1, uiText = UiText.Resource(stringRes = R.string.text)), 11 | ICONS(index = 2, uiText = UiText.Resource(stringRes = R.string.icons)), 12 | COLORED(index = 3, uiText = UiText.Resource(stringRes = R.string.colored)) 13 | } 14 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/appdrawer/AppDrawerItem.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.appdrawer 2 | 3 | import android.graphics.Color 4 | import android.graphics.drawable.Drawable 5 | import androidx.compose.runtime.Immutable 6 | import dev.mslalith.focuslauncher.core.model.app.App 7 | import dev.mslalith.focuslauncher.core.model.extensions.generateHashCode 8 | 9 | @Immutable 10 | data class AppDrawerItem( 11 | val app: App, 12 | val isFavorite: Boolean, 13 | val icon: Drawable, 14 | val color: Color? 15 | ) { 16 | val uniqueKey: Int 17 | get() = listOf(app.packageName, icon, color).generateHashCode() 18 | } 19 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/extensions/UtilityExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.extensions 2 | 3 | fun List.generateHashCode(): Int { 4 | if (isEmpty()) return hashCode() 5 | 6 | var result = first().hashCode() 7 | drop(n = 1).forEach { 8 | result = 31 * result + it.hashCode() 9 | } 10 | return result 11 | } 12 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/location/LatLng.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.location 2 | 3 | import androidx.compose.runtime.Immutable 4 | import kotlinx.serialization.Serializable 5 | 6 | @Immutable 7 | @Serializable 8 | data class LatLng( 9 | val latitude: Double, 10 | val longitude: Double 11 | ) 12 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/lunarphase/LunarPhaseDirection.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.lunarphase 2 | 3 | enum class LunarPhaseDirection { 4 | NEW_TO_FULL, 5 | FULL_TO_NEW 6 | } 7 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/lunarphase/NextPhaseDetails.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.lunarphase 2 | 3 | import androidx.compose.runtime.Immutable 4 | import kotlinx.datetime.LocalDateTime 5 | 6 | @Immutable 7 | data class NextPhaseDetails( 8 | val newMoon: LocalDateTime?, 9 | val fullMoon: LocalDateTime? 10 | ) 11 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/lunarphase/RiseAndSetDetails.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.lunarphase 2 | 3 | import kotlinx.datetime.LocalDateTime 4 | 5 | data class RiseAndSetDetails( 6 | val riseDateTime: LocalDateTime?, 7 | val setDateTime: LocalDateTime? 8 | ) 9 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/lunarphase/UpcomingLunarPhase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.lunarphase 2 | 3 | import androidx.compose.runtime.Immutable 4 | import kotlinx.datetime.LocalDateTime 5 | 6 | @Immutable 7 | data class UpcomingLunarPhase( 8 | val lunarPhase: LunarPhase, 9 | val dateTime: LocalDateTime?, 10 | val isMicroMoon: Boolean, 11 | val isSuperMoon: Boolean 12 | ) 13 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/place/Address.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.place 2 | 3 | data class Address( 4 | val state: String?, 5 | val country: String? 6 | ) 7 | -------------------------------------------------------------------------------- /core/model/src/main/kotlin/dev/mslalith/focuslauncher/core/model/place/Place.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.model.place 2 | 3 | import dev.mslalith.focuslauncher.core.model.location.LatLng 4 | 5 | data class Place( 6 | val id: Long, 7 | val license: String, 8 | val latLng: LatLng, 9 | val displayName: String, 10 | val address: Address 11 | ) { 12 | companion object { 13 | fun default() = Place( 14 | id = -1, 15 | license = "", 16 | latLng = LatLng( 17 | latitude = 0.0, 18 | longitude = 0.0 19 | ), 20 | displayName = "", 21 | address = Address( 22 | state = null, 23 | country = null 24 | ) 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/resources/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/resources/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | } 4 | 5 | android { 6 | namespace = "dev.mslalith.focuslauncher.core.resources" 7 | } 8 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_align_horizontal_center.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_align_horizontal_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_align_horizontal_right.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_app_drawer_colored.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_app_drawer_icons.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_app_drawer_text.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_arrow_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_broom.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_device_mobile.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_drag_indicator.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_grid.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_hand_swipe_left.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_heart.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_house.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_logo_github.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_logo_phosphor.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_logo_twitter.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_map_pin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_map_pin_line.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_moon_stars.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_quote.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_star_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_sun_dim.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/drawable/ic_visibility_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /core/resources/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #313131 4 | -------------------------------------------------------------------------------- /core/screens/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | alias(libs.plugins.kotlin.parcelize) 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.core.screens" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.core.model) 12 | 13 | implementation(libs.circuit.runtime) 14 | } 15 | -------------------------------------------------------------------------------- /core/screens/src/main/kotlin/dev/mslalith/focuslauncher/core/screens/Screens.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.screens 2 | 3 | import com.slack.circuit.runtime.screen.Screen 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data object LauncherScreen : Screen 8 | 9 | @Parcelize 10 | data object HomePageScreen : Screen 11 | 12 | @Parcelize 13 | data object SettingsPageScreen : Screen 14 | 15 | @Parcelize 16 | data object AppDrawerPageScreen : Screen 17 | 18 | @Parcelize 19 | data object EditFavoritesScreen : Screen 20 | 21 | @Parcelize 22 | data object HideAppsScreen: Screen 23 | 24 | @Parcelize 25 | data object CurrentPlaceScreen : Screen 26 | 27 | @Parcelize 28 | data object IconPackScreen : Screen 29 | 30 | @Parcelize 31 | data object AboutScreen : Screen 32 | 33 | @Parcelize 34 | data object DeveloperScreen : Screen 35 | -------------------------------------------------------------------------------- /core/screens/src/main/kotlin/dev/mslalith/focuslauncher/core/screens/UiComponentScreens.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.screens 2 | 3 | import com.slack.circuit.runtime.screen.Screen 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data object ClockWidgetUiComponentScreen : Screen 8 | 9 | @Parcelize 10 | data object LunarCalendarUiComponentScreen : Screen 11 | 12 | @Parcelize 13 | data object QuoteForYouUiComponentScreen : Screen 14 | 15 | @Parcelize 16 | data object FavoritesListUiComponentScreen : Screen 17 | -------------------------------------------------------------------------------- /core/settings/sentry/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.hilt") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.core.settings.sentry" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.core.data) 12 | 13 | implementation(platform(libs.sentry.bom)) 14 | implementation(libs.sentry.android.core) 15 | } 16 | -------------------------------------------------------------------------------- /core/settings/sentry/src/main/kotlin/dev/mslalith/focuslauncher/core/settings/sentry/FakeSentrySettings.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.settings.sentry 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.MutableStateFlow 5 | 6 | class FakeSentrySettings : SentrySettings { 7 | 8 | private val _isEnabledStateFlow = MutableStateFlow(value = true) 9 | override val isEnabled: Flow = _isEnabledStateFlow 10 | 11 | private var sentryDsnInternal: String = "" 12 | 13 | override fun enableSentry() = Unit 14 | 15 | override fun disableSentry() = Unit 16 | 17 | override fun getSentryDsn(): String = sentryDsnInternal 18 | 19 | fun setSentryDsn(dsn: String) { 20 | sentryDsnInternal = dsn 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/settings/sentry/src/main/kotlin/dev/mslalith/focuslauncher/core/settings/sentry/SentrySettings.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.settings.sentry 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | 5 | interface SentrySettings { 6 | val isEnabled: Flow 7 | 8 | fun enableSentry() 9 | fun disableSentry() 10 | fun getSentryDsn(): String 11 | } 12 | -------------------------------------------------------------------------------- /core/settings/sentry/src/main/kotlin/dev/mslalith/focuslauncher/core/settings/sentry/di/SentryModule.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.settings.sentry.di 2 | 3 | import dagger.Binds 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import dev.mslalith.focuslauncher.core.settings.sentry.SentrySettings 8 | import dev.mslalith.focuslauncher.core.settings.sentry.SentrySettingsImpl 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | abstract class SentryModule { 14 | 15 | @Binds 16 | @Singleton 17 | abstract fun bindSentrySettings(sentrySettingsImpl: SentrySettingsImpl): SentrySettings 18 | } 19 | -------------------------------------------------------------------------------- /core/settings/sentry/src/main/kotlin/dev/mslalith/focuslauncher/core/settings/sentry/di/TestSentryModule.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.settings.sentry.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.components.SingletonComponent 6 | import dagger.hilt.testing.TestInstallIn 7 | import dev.mslalith.focuslauncher.core.settings.sentry.FakeSentrySettings 8 | import dev.mslalith.focuslauncher.core.settings.sentry.SentrySettings 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @TestInstallIn( 13 | components = [SingletonComponent::class], 14 | replaces = [SentryModule::class] 15 | ) 16 | internal object TestSentryModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideSentrySettings(): SentrySettings = FakeSentrySettings() 21 | } 22 | -------------------------------------------------------------------------------- /core/testing-circuit/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | } 4 | 5 | android { 6 | namespace = "dev.mslalith.focuslauncher.core.testing.circuit" 7 | 8 | packaging { 9 | resources.excludes.add("META-INF/*") 10 | } 11 | } 12 | 13 | dependencies { 14 | api(projects.core.testing) 15 | 16 | implementation(libs.circuit.foundation) 17 | api(libs.circuit.test) 18 | } 19 | -------------------------------------------------------------------------------- /core/testing-compose/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/testing-compose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.library.compose") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.core.testing.compose" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.core.model) 12 | 13 | implementation(libs.androidx.compose.ui.test) 14 | } 15 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/assertion/AssertBiasAlignment.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.assertion 2 | 3 | import androidx.compose.ui.BiasAlignment 4 | import androidx.compose.ui.test.SemanticsMatcher 5 | import androidx.compose.ui.test.SemanticsNodeInteraction 6 | import androidx.compose.ui.test.assert 7 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 8 | 9 | fun SemanticsNodeInteraction.assertBiasAlignment( 10 | biasAlignment: BiasAlignment.Horizontal 11 | ): SemanticsNodeInteraction = assert( 12 | matcher = SemanticsMatcher.expectValue( 13 | key = TestSemanticsProperties.BiasAlignmentHorizontal, 14 | expectedValue = biasAlignment 15 | ) 16 | ) 17 | 18 | fun SemanticsNodeInteraction.assertBiasAlignment( 19 | biasAlignment: BiasAlignment.Vertical 20 | ): SemanticsNodeInteraction = assert( 21 | matcher = SemanticsMatcher.expectValue( 22 | key = TestSemanticsProperties.BiasAlignmentVertical, 23 | expectedValue = biasAlignment 24 | ) 25 | ) 26 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/assertion/AssertPrimitiveTypes.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.assertion 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import androidx.compose.ui.test.SemanticsNodeInteraction 5 | import androidx.compose.ui.test.assert 6 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 7 | 8 | fun SemanticsNodeInteraction.assertStringType( 9 | value: String 10 | ): SemanticsNodeInteraction = assert( 11 | matcher = SemanticsMatcher.expectValue( 12 | key = TestSemanticsProperties.StringType, 13 | expectedValue = value 14 | ) 15 | ) 16 | 17 | fun SemanticsNodeInteraction.assertBooleanType( 18 | value: Boolean 19 | ): SemanticsNodeInteraction = assert( 20 | matcher = SemanticsMatcher.expectValue( 21 | key = TestSemanticsProperties.BooleanType, 22 | expectedValue = value 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/assertion/AssertSelectedApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.assertion 2 | 3 | import androidx.compose.ui.test.SemanticsNodeInteraction 4 | import androidx.compose.ui.test.assert 5 | import dev.mslalith.focuslauncher.core.model.app.SelectedApp 6 | import dev.mslalith.focuslauncher.core.testing.compose.matcher.hasSelectedApp 7 | 8 | fun SemanticsNodeInteraction.assertSelectedApp( 9 | selectedApp: SelectedApp 10 | ): SemanticsNodeInteraction = assert(matcher = hasSelectedApp(selectedApp = selectedApp)) 11 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/assertion/AssertSelectedHiddenApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.assertion 2 | 3 | import androidx.compose.ui.test.SemanticsNodeInteraction 4 | import androidx.compose.ui.test.assert 5 | import dev.mslalith.focuslauncher.core.model.app.SelectedHiddenApp 6 | import dev.mslalith.focuslauncher.core.testing.compose.matcher.hasSelectedHiddenApp 7 | 8 | fun SemanticsNodeInteraction.assertSelectedHiddenApp( 9 | selectedHiddenApp: SelectedHiddenApp 10 | ): SemanticsNodeInteraction = assert(matcher = hasSelectedHiddenApp(selectedHiddenApp = selectedHiddenApp)) 11 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/assertion/AssertWidgetType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.assertion 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import androidx.compose.ui.test.SemanticsNodeInteraction 5 | import androidx.compose.ui.test.assert 6 | import dev.mslalith.focuslauncher.core.model.WidgetType 7 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 8 | 9 | fun SemanticsNodeInteraction.assertWidgetType( 10 | widgetType: WidgetType 11 | ): SemanticsNodeInteraction = assert( 12 | matcher = SemanticsMatcher.expectValue( 13 | key = TestSemanticsProperties.WidgetType, 14 | expectedValue = widgetType 15 | ) 16 | ) 17 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/extensions/GeneralExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.extensions 2 | 3 | import androidx.compose.ui.test.SemanticsNodeInteraction 4 | import androidx.compose.ui.test.SemanticsNodeInteractionCollection 5 | import androidx.compose.ui.test.performClick 6 | import androidx.compose.ui.test.performScrollTo 7 | import androidx.compose.ui.test.printToString 8 | 9 | fun SemanticsNodeInteractionCollection.printToConsole(maxDepth: Int = 100) { 10 | printToString(maxDepth = maxDepth).let(::println) 11 | } 12 | 13 | fun SemanticsNodeInteraction.printToConsole(maxDepth: Int = 100) { 14 | printToString(maxDepth = maxDepth).let(::println) 15 | } 16 | 17 | fun SemanticsNodeInteraction.performScrollToAndClick() { 18 | performScrollTo() 19 | performClick() 20 | } 21 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/matcher/GeneralMatchers.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.matcher 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import androidx.compose.ui.test.SemanticsNodeInteraction 5 | 6 | fun SemanticsNodeInteraction.onMatchWith( 7 | matcher: SemanticsMatcher 8 | ): Boolean = matcher.matches(node = fetchSemanticsNode()) 9 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/matcher/MatcherPrimitiveTypes.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.matcher 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 5 | 6 | fun hasString( 7 | value: String 8 | ): SemanticsMatcher = SemanticsMatcher.expectValue( 9 | key = TestSemanticsProperties.StringType, 10 | expectedValue = value 11 | ) 12 | 13 | fun hasBoolean( 14 | value: Boolean 15 | ): SemanticsMatcher = SemanticsMatcher.expectValue( 16 | key = TestSemanticsProperties.BooleanType, 17 | expectedValue = value 18 | ) 19 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/matcher/MatcherSelectedApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.matcher 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import dev.mslalith.focuslauncher.core.model.app.SelectedApp 5 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 6 | 7 | fun hasSelectedApp( 8 | selectedApp: SelectedApp 9 | ): SemanticsMatcher = SemanticsMatcher.expectValue( 10 | key = TestSemanticsProperties.SelectedApp, 11 | expectedValue = selectedApp 12 | ) 13 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/matcher/MatcherSelectedHiddenApp.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.matcher 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import dev.mslalith.focuslauncher.core.model.app.SelectedHiddenApp 5 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 6 | 7 | fun hasSelectedHiddenApp( 8 | selectedHiddenApp: SelectedHiddenApp 9 | ): SemanticsMatcher = SemanticsMatcher.expectValue( 10 | key = TestSemanticsProperties.SelectedHiddenApp, 11 | expectedValue = selectedHiddenApp 12 | ) 13 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/matcher/MatcherWidgetType.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.matcher 2 | 3 | import androidx.compose.ui.test.SemanticsMatcher 4 | import dev.mslalith.focuslauncher.core.model.WidgetType 5 | import dev.mslalith.focuslauncher.core.testing.compose.TestSemanticsProperties 6 | 7 | fun hasWidgetType( 8 | widgetType: WidgetType 9 | ): SemanticsMatcher = SemanticsMatcher.expectValue( 10 | key = TestSemanticsProperties.WidgetType, 11 | expectedValue = widgetType 12 | ) 13 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/modifier/testsemantics/TestSemanticsModifier.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics 2 | 3 | import androidx.compose.ui.Modifier 4 | import androidx.compose.ui.platform.testTag 5 | import androidx.compose.ui.semantics.semantics 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.impl.TestSemanticsScopeImpl 7 | 8 | fun Modifier.testSemantics( 9 | tag: String, 10 | block: TestSemanticsScope.() -> Unit = {} 11 | ): Modifier = this then testTag(tag = tag) then semantics { 12 | TestSemanticsScopeImpl(semanticsPropertyReceiver = this).block() 13 | } 14 | -------------------------------------------------------------------------------- /core/testing-compose/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/compose/modifier/testsemantics/TestSemanticsScope.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics 2 | 3 | import androidx.compose.ui.BiasAlignment 4 | import dev.mslalith.focuslauncher.core.model.WidgetType 5 | import dev.mslalith.focuslauncher.core.model.app.SelectedApp 6 | import dev.mslalith.focuslauncher.core.model.app.SelectedHiddenApp 7 | 8 | interface TestSemanticsScope { 9 | fun testString(value: String) 10 | fun testBoolean(value: Boolean) 11 | 12 | fun testBiasAlignment(biasAlignment: BiasAlignment.Horizontal) 13 | fun testBiasAlignment(biasAlignment: BiasAlignment.Vertical) 14 | 15 | fun testSelectedApp(selectedApp: SelectedApp) 16 | fun testSelectedHiddenApp(selectedHiddenApp: SelectedHiddenApp) 17 | 18 | fun testWidgetType(widgetType: WidgetType) 19 | } 20 | -------------------------------------------------------------------------------- /core/testing/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/testing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | } 4 | 5 | android { 6 | namespace = "dev.mslalith.focuslauncher.core.testing" 7 | 8 | packaging { 9 | resources.excludes.add("META-INF/*") 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation(projects.core.model) 15 | api(projects.core.launcherapps) 16 | 17 | api(libs.junit4) 18 | implementation(libs.androidx.test.junit) 19 | api(libs.kotlinx.coroutines.test) 20 | implementation(libs.androidx.core.testing) 21 | api(libs.truth) 22 | api(libs.turbine) 23 | api(libs.mockk) 24 | api(libs.robolectric) 25 | implementation(libs.kotlinx.datetime) 26 | 27 | api(libs.androidx.test.runner) 28 | api(libs.hilt.android.testing) 29 | 30 | api(libs.ktor.client.mock) 31 | implementation(libs.ktor.client.core) 32 | implementation(libs.ktor.client.contentNegotiation) 33 | implementation(libs.ktor.serialization) 34 | } 35 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/AppRobolectricTestRunner.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing 2 | 3 | import org.robolectric.RobolectricTestRunner 4 | import org.robolectric.annotation.Config 5 | 6 | class AppRobolectricTestRunner(testClass: Class<*>?) : RobolectricTestRunner(testClass) { 7 | 8 | override fun buildGlobalConfig(): Config { 9 | return Config.Builder() 10 | // .setSdk(33) 11 | .build() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/CoroutineTest.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing 2 | 3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 4 | import app.cash.turbine.TurbineContext 5 | import app.cash.turbine.turbineScope 6 | import dev.mslalith.focuslauncher.core.testing.rules.TestCoroutineRule 7 | import dev.mslalith.focuslauncher.core.testing.rules.newCoroutineScope 8 | import kotlinx.coroutines.test.TestScope 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Rule 11 | 12 | open class CoroutineTest { 13 | 14 | @get:Rule(order = 1) 15 | val instantExecutorRule = InstantTaskExecutorRule() 16 | 17 | @get:Rule(order = 2) 18 | val coroutineTestRule = TestCoroutineRule() 19 | 20 | protected val testDispatcher = coroutineTestRule.newCoroutineScope() 21 | 22 | protected fun runCoroutineTest( 23 | testBody: suspend context(TurbineContext, TestScope) () -> Unit 24 | ) = testDispatcher.runTest( 25 | testBody = { 26 | turbineScope { testBody(this, this@runTest) } 27 | } 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/HiltTestRunner.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | class HiltTestRunner : AndroidJUnitRunner() { 9 | override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { 10 | return super.newApplication(cl, HiltTestApplication::class.java.name, context) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/extensions/LocaleExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.extensions 2 | 3 | import java.util.Locale 4 | 5 | fun withLocale(locale: Locale, block: () -> Unit) { 6 | val originalLocale = Locale.getDefault() 7 | Locale.setDefault(locale) 8 | block() 9 | Locale.setDefault(originalLocale) 10 | } 11 | -------------------------------------------------------------------------------- /core/testing/src/main/kotlin/dev/mslalith/focuslauncher/core/testing/extensions/TurbineExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.testing.extensions 2 | 3 | import app.cash.turbine.TurbineContext 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | context (TurbineContext) 7 | suspend fun Flow.awaitItem(): T { 8 | val turbine = testIn(scope = this@TurbineContext) 9 | val item = turbine.awaitItem() 10 | turbine.cancel() 11 | return item 12 | } 13 | 14 | context (TurbineContext) 15 | suspend fun Flow.awaitItemChangeUntil( 16 | awaitTill: (T) -> Boolean 17 | ): T { 18 | val turbine = testIn(scope = this@TurbineContext) 19 | var lastItem = turbine.expectMostRecentItem() 20 | 21 | while (!awaitTill(lastItem)) { 22 | lastItem = turbine.awaitItem() 23 | } 24 | 25 | turbine.cancel() 26 | return lastItem 27 | } 28 | -------------------------------------------------------------------------------- /core/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.library") 3 | id("focuslauncher.android.library.compose") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.core.ui" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.core.common) 13 | implementation(projects.core.model) 14 | implementation(projects.core.resources) 15 | 16 | implementation(libs.androidx.activity.compose) 17 | implementation(libs.androidx.lifecycle.runtime.compose) 18 | implementation(libs.androidx.navigation.compose) 19 | implementation(libs.kotlinx.collections.immutable) 20 | } 21 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/dev/mslalith/focuslauncher/core/ui/AppBarWithBackIcon.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.ui 2 | 3 | import androidx.compose.foundation.layout.RowScope 4 | import androidx.compose.material3.ExperimentalMaterial3Api 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TopAppBar 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.res.stringResource 9 | 10 | @OptIn(ExperimentalMaterial3Api::class) 11 | @Composable 12 | fun AppBarWithBackIcon( 13 | title: String, 14 | onBackPressed: () -> Unit, 15 | actions: @Composable RowScope.() -> Unit = {} 16 | ) { 17 | TopAppBar( 18 | navigationIcon = { 19 | RoundIcon( 20 | iconRes = R.drawable.ic_arrow_left, 21 | contentDescription = stringResource(id = R.string.go_back), 22 | onClick = onBackPressed 23 | ) 24 | }, 25 | title = { 26 | Text(text = title) 27 | }, 28 | actions = actions 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/dev/mslalith/focuslauncher/core/ui/Spacer.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.ui 2 | 3 | import androidx.compose.foundation.layout.ColumnScope 4 | import androidx.compose.foundation.layout.RowScope 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.width 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.unit.Dp 11 | 12 | @Composable 13 | fun HorizontalSpacer(spacing: Dp) = Spacer(modifier = Modifier.width(width = spacing)) 14 | 15 | @Composable 16 | fun VerticalSpacer(spacing: Dp) = Spacer(modifier = Modifier.height(height = spacing)) 17 | 18 | @Composable 19 | fun RowScope.FillSpacer() = Spacer(modifier = Modifier.weight(weight = 1f)) 20 | 21 | @Composable 22 | fun ColumnScope.FillSpacer() = Spacer(modifier = Modifier.weight(weight = 1f)) 23 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/dev/mslalith/focuslauncher/core/ui/effects/OnLifecycleEventChange.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.ui.effects 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisposableEffect 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.rememberUpdatedState 7 | import androidx.compose.ui.platform.LocalLifecycleOwner 8 | import androidx.lifecycle.Lifecycle 9 | import androidx.lifecycle.LifecycleEventObserver 10 | 11 | @Composable 12 | fun OnLifecycleEventChange( 13 | onEvent: (Lifecycle.Event) -> Unit 14 | ) { 15 | val updatedOnEvent by rememberUpdatedState(newValue = onEvent) 16 | val lifecycleOwner by rememberUpdatedState(newValue = LocalLifecycleOwner.current) 17 | 18 | DisposableEffect(key1 = lifecycleOwner) { 19 | val lifecycle = lifecycleOwner.lifecycle 20 | val observer = LifecycleEventObserver { _, event -> 21 | updatedOnEvent(event) 22 | } 23 | 24 | lifecycle.addObserver(observer = observer) 25 | onDispose { lifecycle.removeObserver(observer = observer) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/dev/mslalith/focuslauncher/core/ui/extensions/ScaffoldExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.ui.extensions 2 | 3 | import androidx.compose.material3.SnackbarDuration 4 | import androidx.compose.material3.SnackbarHostState 5 | import androidx.compose.material3.SnackbarResult 6 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 7 | 8 | @IgnoreInKoverReport 9 | suspend fun SnackbarHostState.showDismissibleSnackbar( 10 | message: String, 11 | duration: SnackbarDuration = SnackbarDuration.Short, 12 | dismissVisibleSnackbar: Boolean = true, 13 | discardIfShowing: Boolean = false, 14 | actionLabel: String? = null, 15 | onAction: ((SnackbarResult) -> Unit)? = null 16 | ) { 17 | if (discardIfShowing && currentSnackbarData != null) return 18 | 19 | if (dismissVisibleSnackbar) { 20 | currentSnackbarData?.dismiss() 21 | } 22 | 23 | val snackbarResult = showSnackbar( 24 | message = message, 25 | actionLabel = actionLabel, 26 | duration = duration 27 | ) 28 | onAction?.invoke(snackbarResult) 29 | } 30 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/dev/mslalith/focuslauncher/core/ui/extensions/UiTextExtensions.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.ui.extensions 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.res.stringResource 5 | import dev.mslalith.focuslauncher.core.model.UiText 6 | 7 | @Composable 8 | fun UiText.string(): String = when (this) { 9 | is UiText.Static -> text 10 | is UiText.Resource -> stringResource(id = stringRes) 11 | } 12 | -------------------------------------------------------------------------------- /core/ui/src/main/kotlin/dev/mslalith/focuslauncher/core/ui/providers/ProvideLauncherPagerState.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.core.ui.providers 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.pager.PagerState 5 | import androidx.compose.foundation.pager.rememberPagerState 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.CompositionLocalProvider 8 | import androidx.compose.runtime.compositionLocalOf 9 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 10 | 11 | @OptIn(ExperimentalFoundationApi::class) 12 | @IgnoreInKoverReport 13 | val LocalLauncherPagerState = compositionLocalOf { 14 | error("No PagerState provided") 15 | } 16 | 17 | @OptIn(ExperimentalFoundationApi::class) 18 | @Composable 19 | fun ProvideLauncherPagerState( 20 | content: @Composable () -> Unit 21 | ) { 22 | val pagerState = rememberPagerState( 23 | initialPage = 1, 24 | pageCount = { 3 } 25 | ) 26 | CompositionLocalProvider(LocalLauncherPagerState provides pagerState) { 27 | content() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feature/appdrawerpage/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/appdrawerpage/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.feature.appdrawerpage" 8 | } 9 | 10 | dependencies { 11 | implementation(libs.kotlinx.collections.immutable) 12 | } 13 | -------------------------------------------------------------------------------- /feature/appdrawerpage/src/main/kotlin/dev/mslalith/focuslauncher/feature/appdrawerpage/bottomsheet/moreoptions/AppMoreOptionsBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.appdrawerpage.bottomsheet.moreoptions 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.app.App 6 | import dev.mslalith.focuslauncher.core.model.appdrawer.AppDrawerItem 7 | 8 | data class AppMoreOptionsBottomSheetState( 9 | val appDrawerItem: AppDrawerItem, 10 | val eventSink: (AppMoreOptionsBottomSheetUiEvent) -> Unit 11 | ) : CircuitUiState 12 | 13 | sealed interface AppMoreOptionsBottomSheetUiEvent : CircuitUiEvent { 14 | data object GoBack : AppMoreOptionsBottomSheetUiEvent 15 | data class AddToFavorites(val app: App) : AppMoreOptionsBottomSheetUiEvent 16 | data class RemoveFromFavorites(val app: App) : AppMoreOptionsBottomSheetUiEvent 17 | data class AddToHiddenApps(val app: App, val removeFromFavorites: Boolean) : AppMoreOptionsBottomSheetUiEvent 18 | data class ClickUpdateDisplayName(val app: App) : AppMoreOptionsBottomSheetUiEvent 19 | } 20 | -------------------------------------------------------------------------------- /feature/appdrawerpage/src/main/kotlin/dev/mslalith/focuslauncher/feature/appdrawerpage/bottomsheet/updateappdisplayname/UpdateAppDisplayNameBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.appdrawerpage.bottomsheet.updateappdisplayname 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.app.App 6 | 7 | data class UpdateAppDisplayNameBottomSheetState( 8 | val app: App, 9 | val eventSink: (UpdateAppDisplayNameBottomSheetUiEvent) -> Unit 10 | ) : CircuitUiState 11 | 12 | sealed interface UpdateAppDisplayNameBottomSheetUiEvent : CircuitUiEvent { 13 | data class UpdateDisplayName(val displayName: String) : UpdateAppDisplayNameBottomSheetUiEvent 14 | } 15 | -------------------------------------------------------------------------------- /feature/appdrawerpage/src/main/kotlin/dev/mslalith/focuslauncher/feature/appdrawerpage/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.appdrawerpage.utils 2 | 3 | import androidx.compose.ui.unit.dp 4 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 5 | 6 | @IgnoreInKoverReport 7 | internal object Constants { 8 | val ITEM_START_PADDING = 24.dp 9 | val ITEM_END_PADDING = 8.dp 10 | val APP_ICON_SIZE = 28.dp 11 | val ICON_INNER_HORIZONTAL_PADDING = 4.dp 12 | } 13 | -------------------------------------------------------------------------------- /feature/clock24/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/clock24/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.feature.clock24" 9 | } 10 | 11 | dependencies { 12 | implementation(libs.kotlinx.datetime) 13 | implementation(libs.kotlinx.collections.immutable) 14 | } 15 | -------------------------------------------------------------------------------- /feature/clock24/src/main/kotlin/dev/mslalith/focuslauncher/feature/clock24/model/AnalogClockHandlePhase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.clock24.model 2 | 3 | internal enum class AnalogClockHandlePhase(val angle: Double) { 4 | NONE(angle = 135.0), 5 | TOP(angle = 270.0), 6 | RIGHT(angle = 0.0), 7 | BOTTOM(angle = 90.0), 8 | LEFT(angle = 180.0) 9 | } 10 | -------------------------------------------------------------------------------- /feature/clock24/src/main/kotlin/dev/mslalith/focuslauncher/feature/clock24/utils/TestTags.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.clock24.utils 2 | 3 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 4 | 5 | @IgnoreInKoverReport 6 | internal object TestTags { 7 | const val TAG_CLOCK_COLUMN = "tag_clock_column" 8 | const val TAG_CLOCK24 = "tag_clock24" 9 | const val TAG_REGULAR_CLOCK = "tag_regular_clock" 10 | } 11 | -------------------------------------------------------------------------------- /feature/clock24/src/main/kotlin/dev/mslalith/focuslauncher/feature/clock24/widget/ClockWidgetUiComponentContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.clock24.widget 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.ClockAlignment 6 | 7 | data class ClockWidgetUiComponentState( 8 | val currentTime: String, 9 | val showClock24: Boolean, 10 | val use24Hour: Boolean, 11 | val clockAlignment: ClockAlignment, 12 | val clock24AnimationDuration: Int, 13 | val eventSink: (ClockWidgetUiComponentUiEvent) -> Unit 14 | ) : CircuitUiState 15 | 16 | sealed interface ClockWidgetUiComponentUiEvent : CircuitUiEvent { 17 | data object RefreshTime : ClockWidgetUiComponentUiEvent 18 | } 19 | -------------------------------------------------------------------------------- /feature/favorites/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/favorites/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.feature.favorites" 8 | } 9 | 10 | dependencies { 11 | implementation(libs.androidx.palette.ktx) 12 | implementation(libs.kotlinx.collections.immutable) 13 | implementation(libs.reorderable) 14 | } 15 | -------------------------------------------------------------------------------- /feature/favorites/src/main/kotlin/dev/mslalith/focuslauncher/feature/favorites/FavoritesListUiComponentContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.favorites 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.app.AppWithColor 6 | import kotlinx.collections.immutable.ImmutableList 7 | 8 | data class FavoritesListUiComponentState( 9 | val favoritesList: ImmutableList, 10 | val eventSink: (FavoritesListUiComponentUiEvent) -> Unit 11 | ) : CircuitUiState 12 | 13 | sealed interface FavoritesListUiComponentUiEvent : CircuitUiEvent { 14 | data object AddDefaultAppsIfRequired : FavoritesListUiComponentUiEvent 15 | } 16 | -------------------------------------------------------------------------------- /feature/favorites/src/main/kotlin/dev/mslalith/focuslauncher/feature/favorites/bottomsheet/FavoritesBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.favorites.bottomsheet 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.app.AppWithColor 6 | import kotlinx.collections.immutable.ImmutableList 7 | 8 | data class FavoritesBottomSheetState( 9 | val favoritesList: ImmutableList, 10 | val eventSink: (FavoritesBottomSheetUiEvent) -> Unit 11 | ) : CircuitUiState 12 | 13 | sealed interface FavoritesBottomSheetUiEvent : CircuitUiEvent { 14 | data class Move(val fromIndex: Int, val toIndex: Int) : FavoritesBottomSheetUiEvent 15 | data class Remove(val appWithColor: AppWithColor) : FavoritesBottomSheetUiEvent 16 | data object Save : FavoritesBottomSheetUiEvent 17 | } 18 | -------------------------------------------------------------------------------- /feature/homepage/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/homepage/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.feature.homepage" 8 | } 9 | 10 | dependencies { 11 | implementation(projects.feature.clock24) 12 | implementation(projects.feature.lunarcalendar) 13 | implementation(projects.feature.quoteforyou) 14 | implementation(projects.feature.favorites) 15 | } 16 | -------------------------------------------------------------------------------- /feature/homepage/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /feature/homepage/src/main/kotlin/dev/mslalith/focuslauncher/feature/homepage/HomePageContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.homepage 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.feature.clock24.widget.ClockWidgetUiComponentState 6 | import dev.mslalith.focuslauncher.feature.favorites.FavoritesListUiComponentState 7 | import dev.mslalith.focuslauncher.feature.lunarcalendar.widget.LunarCalendarUiComponentState 8 | import dev.mslalith.focuslauncher.feature.quoteforyou.widget.QuoteForYouUiComponentState 9 | 10 | data class HomePageState( 11 | val isPullDownNotificationShadeEnabled: Boolean, 12 | val clockWidgetUiComponentState: ClockWidgetUiComponentState, 13 | val lunarCalendarUiComponentState: LunarCalendarUiComponentState, 14 | val quoteForYouUiComponentState: QuoteForYouUiComponentState, 15 | val favoritesListUiComponentState: FavoritesListUiComponentState 16 | ) : CircuitUiState 17 | 18 | sealed interface HomePageUiEvent : CircuitUiEvent 19 | -------------------------------------------------------------------------------- /feature/homepage/src/main/kotlin/dev/mslalith/focuslauncher/feature/homepage/model/HomePadding.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.homepage.model 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.runtime.compositionLocalOf 5 | import androidx.compose.ui.unit.Dp 6 | import androidx.compose.ui.unit.dp 7 | 8 | internal val LocalHomePadding = compositionLocalOf { 9 | error("No LocalHomePadding provided") 10 | } 11 | 12 | internal data class HomePadding( 13 | val contentPaddingValues: PaddingValues = PaddingValues( 14 | start = 22.dp, 15 | end = 22.dp, 16 | top = 16.dp, 17 | bottom = 22.dp 18 | ), 19 | val lunarPhaseIconSize: Dp = 40.dp, 20 | val favoriteActionItemSize: Dp = 16.dp 21 | ) 22 | -------------------------------------------------------------------------------- /feature/lunarcalendar/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/lunarcalendar/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.feature.lunarcalendar" 8 | } 9 | 10 | dependencies { 11 | implementation(libs.kotlinx.datetime) 12 | } 13 | -------------------------------------------------------------------------------- /feature/lunarcalendar/src/main/kotlin/dev/mslalith/focuslauncher/feature/lunarcalendar/bottomsheet/lunarphasedetails/LunarPhaseDetailsBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.lunarcalendar.bottomsheet.lunarphasedetails 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.common.model.State 6 | import dev.mslalith.focuslauncher.core.model.lunarphase.LunarPhaseDetails 7 | 8 | data class LunarPhaseDetailsBottomSheetState( 9 | val lunarPhaseDetails: State 10 | ) : CircuitUiState 11 | 12 | sealed interface LunarPhaseDetailsBottomSheetUiEvent : CircuitUiEvent 13 | -------------------------------------------------------------------------------- /feature/lunarcalendar/src/main/kotlin/dev/mslalith/focuslauncher/feature/lunarcalendar/bottomsheet/lunarphasedetails/ui/RiseAndSetHeaders.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.lunarcalendar.bottomsheet.lunarphasedetails.ui 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.Text 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.res.stringResource 9 | import dev.mslalith.focuslauncher.core.ui.FillSpacer 10 | import dev.mslalith.focuslauncher.feature.lunarcalendar.R 11 | 12 | @Composable 13 | internal fun RiseAndSetHeaders( 14 | contentColor: Color 15 | ) { 16 | Row { 17 | Text( 18 | text = stringResource(id = R.string.moon), 19 | color = contentColor, 20 | style = MaterialTheme.typography.titleMedium 21 | ) 22 | FillSpacer() 23 | Text( 24 | text = stringResource(id = R.string.sun), 25 | color = contentColor, 26 | style = MaterialTheme.typography.titleMedium 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feature/lunarcalendar/src/main/kotlin/dev/mslalith/focuslauncher/feature/lunarcalendar/bottomsheet/lunarphasedetails/ui/RiseAndSetIndicator.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.lunarcalendar.bottomsheet.lunarphasedetails.ui 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.RowScope 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | 12 | @Composable 13 | internal fun RowScope.RiseAndSetIndicator( 14 | text: String, 15 | contentColor: Color 16 | ) { 17 | Box( 18 | modifier = Modifier.weight(weight = 1f), 19 | contentAlignment = Alignment.Center 20 | ) { 21 | Text( 22 | text = text, 23 | color = contentColor, 24 | style = MaterialTheme.typography.bodySmall 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feature/lunarcalendar/src/main/kotlin/dev/mslalith/focuslauncher/feature/lunarcalendar/bottomsheet/lunarphasedetails/ui/TodayLunarMoonIconAndPhase.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.lunarcalendar.bottomsheet.lunarphasedetails.ui 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.unit.Dp 6 | import dev.mslalith.focuslauncher.core.model.lunarphase.LunarPhaseDetails 7 | import dev.mslalith.focuslauncher.feature.lunarcalendar.shared.LunarPhaseMoonIcon 8 | 9 | @Composable 10 | internal fun TodayLunarMoonIconAndPhase( 11 | lunarPhaseDetails: LunarPhaseDetails, 12 | moonSize: Dp 13 | ) { 14 | Column { 15 | LunarPhaseMoonIcon( 16 | phaseAngle = lunarPhaseDetails.phaseAngle, 17 | illumination = lunarPhaseDetails.illumination, 18 | moonSize = moonSize * 0.3f 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /feature/lunarcalendar/src/main/kotlin/dev/mslalith/focuslauncher/feature/lunarcalendar/widget/LunarCalendarUiComponentContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.lunarcalendar.widget 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.common.model.State 6 | import dev.mslalith.focuslauncher.core.model.lunarphase.LunarPhaseDetails 7 | import dev.mslalith.focuslauncher.core.model.lunarphase.UpcomingLunarPhase 8 | 9 | data class LunarCalendarUiComponentState( 10 | val showLunarPhase: Boolean, 11 | val showIlluminationPercent: Boolean, 12 | val showUpcomingPhaseDetails: Boolean, 13 | val lunarPhaseDetails: State, 14 | val upcomingLunarPhase: State 15 | ) : CircuitUiState 16 | 17 | sealed interface LunarCalendarUiComponentUiEvent : CircuitUiEvent 18 | -------------------------------------------------------------------------------- /feature/quoteforyou/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/quoteforyou/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.feature.quoteforyou" 8 | } 9 | -------------------------------------------------------------------------------- /feature/quoteforyou/src/main/kotlin/dev/mslalith/focuslauncher/feature/quoteforyou/bottomsheet/quotewidgetsettings/QuoteWidgetSettingsBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.quoteforyou.bottomsheet.quotewidgetsettings 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.common.model.State 6 | import dev.mslalith.focuslauncher.core.model.Quote 7 | 8 | data class QuoteWidgetSettingsBottomSheetState( 9 | val showQuotes: Boolean, 10 | val isFetchingQuotes: Boolean, 11 | val currentQuote: State, 12 | val eventSink: (QuoteWidgetSettingsBottomSheetUiEvent) -> Unit 13 | ) : CircuitUiState 14 | 15 | sealed interface QuoteWidgetSettingsBottomSheetUiEvent : CircuitUiEvent { 16 | data object ToggleShowQuoteWidget : QuoteWidgetSettingsBottomSheetUiEvent 17 | data object FetchQuoteWidget : QuoteWidgetSettingsBottomSheetUiEvent 18 | data object FetchNextQuote : QuoteWidgetSettingsBottomSheetUiEvent 19 | } 20 | -------------------------------------------------------------------------------- /feature/quoteforyou/src/main/kotlin/dev/mslalith/focuslauncher/feature/quoteforyou/widget/QuoteForYouUiComponentContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.quoteforyou.widget 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.common.model.State 6 | import dev.mslalith.focuslauncher.core.model.Quote 7 | 8 | data class QuoteForYouUiComponentState( 9 | val showQuotes: Boolean, 10 | val currentQuote: State, 11 | val eventSink: (QuoteForYouUiComponentUiEvent) -> Unit 12 | ) : CircuitUiState 13 | 14 | sealed interface QuoteForYouUiComponentUiEvent : CircuitUiEvent { 15 | data object FetchNextQuote : QuoteForYouUiComponentUiEvent 16 | } 17 | -------------------------------------------------------------------------------- /feature/settingspage/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/settingspage/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.feature.settingspage" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.feature.theme) 13 | implementation(projects.feature.clock24) 14 | implementation(projects.feature.lunarcalendar) 15 | implementation(projects.feature.quoteforyou) 16 | 17 | implementation(libs.kotlinx.collections.immutable) 18 | 19 | testImplementation(projects.core.settings.sentry) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/SettingsPageContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage 2 | 3 | import android.content.Context 4 | import com.slack.circuit.runtime.CircuitUiEvent 5 | import com.slack.circuit.runtime.CircuitUiState 6 | import com.slack.circuit.runtime.screen.Screen 7 | 8 | data class SettingsPageState( 9 | val showStatusBar: Boolean, 10 | val canDrawNotificationShade: Boolean, 11 | val showIconPack: Boolean, 12 | val isDefaultLauncher: Boolean, 13 | val showDeveloperOption: Boolean, 14 | val eventSink: (SettingsPageUiEvent) -> Unit 15 | ) : CircuitUiState 16 | 17 | sealed interface SettingsPageUiEvent : CircuitUiEvent { 18 | data object ToggleStatusBarVisibility : SettingsPageUiEvent 19 | data object ToggleNotificationShade : SettingsPageUiEvent 20 | data class RefreshIsDefaultLauncher(val context: Context) : SettingsPageUiEvent 21 | data class GoTo(val screen: Screen) : SettingsPageUiEvent 22 | data class OnBottomSheetOpened(val screen: Screen) : SettingsPageUiEvent 23 | } 24 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/bottomsheet/privacy/PrivacySettingsBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.bottomsheet.privacy 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | 6 | data class PrivacySettingsBottomSheetState( 7 | val reportCrashes: Boolean, 8 | val eventSink: (PrivacySettingsBottomSheetUiEvent) -> Unit 9 | ) : CircuitUiState 10 | 11 | sealed interface PrivacySettingsBottomSheetUiEvent : CircuitUiEvent { 12 | data object ToggleReportCrashes : PrivacySettingsBottomSheetUiEvent 13 | } 14 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/About.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun About( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_ABOUT), 17 | text = stringResource(id = R.string.about), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/AppDrawer.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun AppDrawer( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_APP_DRAWER), 17 | text = stringResource(id = R.string.app_drawer), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/ChangeTheme.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun ChangeTheme( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_CHANGE_THEME), 17 | text = stringResource(id = R.string.change_theme), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/Developer.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun Developer( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_DEVELOPER), 17 | text = stringResource(id = R.string.developer), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/EditFavorites.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun EditFavorites( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_EDIT_FAVORITES), 17 | text = stringResource(id = R.string.edit_favorites), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/HideApps.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun HideApps( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_HIDE_APPS), 17 | text = stringResource(id = R.string.hide_apps), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/settingspage/src/main/kotlin/dev/mslalith/focuslauncher/feature/settingspage/settingsitems/Privacy.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.settingspage.settingsitems 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.res.stringResource 6 | import dev.mslalith.focuslauncher.core.testing.compose.modifier.testsemantics.testSemantics 7 | import dev.mslalith.focuslauncher.feature.settingspage.R 8 | import dev.mslalith.focuslauncher.feature.settingspage.shared.SettingsItem 9 | import dev.mslalith.focuslauncher.feature.settingspage.utils.TestTags 10 | 11 | @Composable 12 | internal fun Privacy( 13 | onClick: () -> Unit 14 | ) { 15 | SettingsItem( 16 | modifier = Modifier.testSemantics(tag = TestTags.ITEM_PRIVACY), 17 | text = stringResource(id = R.string.privacy), 18 | onClick = onClick 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /feature/theme/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/theme/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | } 5 | 6 | android { 7 | namespace = "dev.mslalith.focuslauncher.feature.theme" 8 | } 9 | 10 | dependencies { 11 | implementation(libs.kotlinx.collections.immutable) 12 | } 13 | -------------------------------------------------------------------------------- /feature/theme/src/main/kotlin/dev/mslalith/focuslauncher/feature/theme/LauncherThemeContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.theme 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.Theme 6 | 7 | data class LauncherThemeState( 8 | val theme: Theme 9 | ) : CircuitUiState 10 | 11 | sealed interface LauncherThemeUiEvent : CircuitUiEvent 12 | -------------------------------------------------------------------------------- /feature/theme/src/main/kotlin/dev/mslalith/focuslauncher/feature/theme/LauncherThemePresenter.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.theme 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.remember 6 | import com.slack.circuit.retained.collectAsRetainedState 7 | import com.slack.circuit.runtime.presenter.Presenter 8 | import dev.mslalith.focuslauncher.core.domain.theme.GetThemeUseCase 9 | import dev.mslalith.focuslauncher.core.model.Constants.Defaults.Settings.General.DEFAULT_THEME 10 | import javax.inject.Inject 11 | 12 | class LauncherThemePresenter @Inject constructor( 13 | private val getThemeUseCase: GetThemeUseCase 14 | ) : Presenter { 15 | 16 | @Composable 17 | override fun present(): LauncherThemeState { 18 | val currentTheme by remember { getThemeUseCase() }.collectAsRetainedState(initial = DEFAULT_THEME) 19 | 20 | return LauncherThemeState( 21 | theme = currentTheme 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /feature/theme/src/main/kotlin/dev/mslalith/focuslauncher/feature/theme/bottomsheet/themeselection/ThemeSelectionBottomSheetContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.theme.bottomsheet.themeselection 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.Theme 6 | import dev.mslalith.focuslauncher.feature.theme.model.ThemeWithIcon 7 | import kotlinx.collections.immutable.ImmutableList 8 | 9 | data class ThemeSelectionBottomSheetState( 10 | val currentTheme: Theme, 11 | val allThemes: ImmutableList, 12 | val eventSink: (ThemeSelectionBottomSheetUiEvent) -> Unit 13 | ) : CircuitUiState 14 | 15 | sealed interface ThemeSelectionBottomSheetUiEvent : CircuitUiEvent { 16 | data class SelectedTheme(val theme: Theme?) : ThemeSelectionBottomSheetUiEvent 17 | } 18 | -------------------------------------------------------------------------------- /feature/theme/src/main/kotlin/dev/mslalith/focuslauncher/feature/theme/model/ThemeWithIcon.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.feature.theme.model 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.compose.runtime.Immutable 5 | import dev.mslalith.focuslauncher.core.model.Theme 6 | 7 | @Immutable 8 | data class ThemeWithIcon( 9 | val theme: Theme, 10 | @DrawableRes val iconRes: Int 11 | ) 12 | -------------------------------------------------------------------------------- /feature/theme/src/main/res/font/nunitosans_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/feature/theme/src/main/res/font/nunitosans_regular.ttf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /images/animated_clock.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/animated_clock.mp4 -------------------------------------------------------------------------------- /images/change_theme.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/change_theme.mp4 -------------------------------------------------------------------------------- /images/edit_favorites.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/edit_favorites.mp4 -------------------------------------------------------------------------------- /images/hide_apps.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/hide_apps.mp4 -------------------------------------------------------------------------------- /images/icon_pack.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/icon_pack.mp4 -------------------------------------------------------------------------------- /images/lunar_phase_info.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/lunar_phase_info.mp4 -------------------------------------------------------------------------------- /images/what_is.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mslalith/focus_launcher/e12448d99d8d8f2980eca8af960aee0745c2f468/images/what_is.png -------------------------------------------------------------------------------- /screens/about/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /screens/about/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.screens.about" 9 | } 10 | -------------------------------------------------------------------------------- /screens/about/src/main/kotlin/dev/mslalith/focuslauncher/screens/about/AboutContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.about 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | 6 | data class AboutState( 7 | val eventSink: (AboutUiEvent) -> Unit 8 | ) : CircuitUiState 9 | 10 | sealed interface AboutUiEvent : CircuitUiEvent { 11 | data object GoBack : AboutUiEvent 12 | } 13 | -------------------------------------------------------------------------------- /screens/about/src/main/kotlin/dev/mslalith/focuslauncher/screens/about/AboutPresenter.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.about 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.slack.circuit.codegen.annotations.CircuitInject 5 | import com.slack.circuit.runtime.Navigator 6 | import com.slack.circuit.runtime.presenter.Presenter 7 | import dagger.assisted.Assisted 8 | import dagger.assisted.AssistedInject 9 | import dagger.hilt.components.SingletonComponent 10 | import dev.mslalith.focuslauncher.core.screens.AboutScreen 11 | 12 | @CircuitInject(AboutScreen::class, SingletonComponent::class) 13 | class AboutPresenter @AssistedInject constructor( 14 | @Assisted private val navigator: Navigator 15 | ) : Presenter { 16 | 17 | @Composable 18 | override fun present(): AboutState { 19 | return AboutState { 20 | when (it) { 21 | AboutUiEvent.GoBack -> navigator.pop() 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /screens/about/src/test/kotlin/dev/mslalith/focuslauncher/screens/about/AboutPresenterTest.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.about 2 | 3 | import dev.mslalith.focuslauncher.core.testing.AppRobolectricTestRunner 4 | import dev.mslalith.focuslauncher.core.testing.circuit.PresenterTest 5 | import org.junit.FixMethodOrder 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | import org.junit.runners.MethodSorters 9 | 10 | @RunWith(AppRobolectricTestRunner::class) 11 | @FixMethodOrder(value = MethodSorters.NAME_ASCENDING) 12 | class AboutPresenterTest : PresenterTest() { 13 | 14 | override fun presenterUnderTest() = AboutPresenter( 15 | navigator = navigator 16 | ) 17 | 18 | @Test 19 | fun `01 - verify pop on back press`() = runPresenterTest { 20 | val state = awaitItem() 21 | state.eventSink(AboutUiEvent.GoBack) 22 | navigator.awaitPop() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /screens/currentplace/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /screens/currentplace/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.screens.currentplace" 9 | } 10 | 11 | dependencies { 12 | implementation(libs.osmdroid) 13 | } 14 | -------------------------------------------------------------------------------- /screens/currentplace/src/main/kotlin/dev/mslalith/focuslauncher/screens/currentplace/CurrentPlaceContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.currentplace 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.common.model.LoadingState 6 | import dev.mslalith.focuslauncher.core.model.location.LatLng 7 | 8 | data class CurrentPlaceState( 9 | val latLng: LatLng, 10 | val initialLatLng: LatLng, 11 | val addressState: LoadingState, 12 | val isOnline: Boolean, 13 | val canSave: Boolean, 14 | val eventSink: (CurrentPlaceUiEvent) -> Unit 15 | ) : CircuitUiState 16 | 17 | sealed interface CurrentPlaceUiEvent : CircuitUiEvent { 18 | data object GoBack : CurrentPlaceUiEvent 19 | data object SavePlace : CurrentPlaceUiEvent 20 | data class UpdateCurrentLatLng(val latLng: LatLng) : CurrentPlaceUiEvent 21 | } 22 | -------------------------------------------------------------------------------- /screens/developer/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | alias(libs.plugins.kotlin.serialization) 6 | } 7 | 8 | android { 9 | namespace = "dev.mslalith.focuslauncher.screens.developer" 10 | } 11 | 12 | dependencies { 13 | implementation(libs.kotlinx.serialization) 14 | } 15 | -------------------------------------------------------------------------------- /screens/developer/src/main/kotlin/dev/mslalith/focuslauncher/screens/developer/DeveloperContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.developer 2 | 3 | import android.net.Uri 4 | import com.slack.circuit.runtime.CircuitUiEvent 5 | import com.slack.circuit.runtime.CircuitUiState 6 | 7 | data class DeveloperState( 8 | val defaultFavoritesName: String, 9 | val isFavoritesReading: Boolean, 10 | val isFavoritesSaving: Boolean, 11 | val eventSink: (DeveloperUiEvent) -> Unit 12 | ) : CircuitUiState 13 | 14 | sealed interface DeveloperUiEvent : CircuitUiEvent { 15 | data object GoBack : DeveloperUiEvent 16 | 17 | data class ReadFavoritesFromUri(val uri: Uri) : DeveloperUiEvent 18 | data class SaveFavoritesFromUri(val uri: Uri) : DeveloperUiEvent 19 | } 20 | -------------------------------------------------------------------------------- /screens/developer/src/main/kotlin/dev/mslalith/focuslauncher/screens/developer/file/FavoritesCacheFileInteractor.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.developer.file 2 | 3 | import android.content.Context 4 | import dagger.hilt.android.qualifiers.ApplicationContext 5 | import dev.mslalith.focuslauncher.core.model.app.App 6 | import kotlinx.serialization.builtins.ListSerializer 7 | import kotlinx.serialization.json.Json 8 | import javax.inject.Inject 9 | 10 | class FavoritesCacheFileInteractor @Inject constructor( 11 | @ApplicationContext private val context: Context 12 | ) : CacheFileInteractor>( 13 | context = context 14 | ) { 15 | 16 | override val fileName: String = "saved_favorites.json" 17 | 18 | override fun default(): List = emptyList() 19 | 20 | override fun List.toJson(): String = Json.encodeToString( 21 | serializer = ListSerializer(elementSerializer = App.serializer()), 22 | value = this 23 | ) 24 | 25 | override fun fromJson(json: String): List = Json.decodeFromString(string = json) 26 | } 27 | -------------------------------------------------------------------------------- /screens/editfavorites/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /screens/editfavorites/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.screens.editfavorites" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.core.ui) 13 | 14 | implementation(libs.kotlinx.collections.immutable) 15 | } 16 | -------------------------------------------------------------------------------- /screens/editfavorites/src/main/kotlin/dev/mslalith/focuslauncher/screens/editfavorites/EditFavoritesContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.editfavorites 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.app.App 6 | import dev.mslalith.focuslauncher.core.model.app.SelectedApp 7 | import kotlinx.collections.immutable.ImmutableList 8 | 9 | data class EditFavoritesState( 10 | val favoriteApps: ImmutableList, 11 | val showHiddenApps: Boolean, 12 | val eventSink: (EditFavoritesUiEvent) -> Unit 13 | ) : CircuitUiState 14 | 15 | sealed interface EditFavoritesUiEvent : CircuitUiEvent { 16 | data class AddToFavorites(val app: App) : EditFavoritesUiEvent 17 | data class RemoveFromFavorites(val app: App) : EditFavoritesUiEvent 18 | data object ClearFavorites : EditFavoritesUiEvent 19 | 20 | data object ToggleShowingHiddenApps : EditFavoritesUiEvent 21 | data object GoBack : EditFavoritesUiEvent 22 | } 23 | -------------------------------------------------------------------------------- /screens/editfavorites/src/main/kotlin/dev/mslalith/focuslauncher/screens/editfavorites/ui/FavoriteListItem.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.editfavorites.ui 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.Modifier 5 | import dev.mslalith.focuslauncher.core.model.app.SelectedApp 6 | import dev.mslalith.focuslauncher.core.ui.SelectableCheckboxItem 7 | 8 | @Composable 9 | internal fun FavoriteListItem( 10 | modifier: Modifier = Modifier, 11 | selectedApp: SelectedApp, 12 | onAppClick: () -> Unit 13 | ) { 14 | SelectableCheckboxItem( 15 | modifier = modifier, 16 | text = selectedApp.app.displayName, 17 | checked = selectedApp.isSelected, 18 | disabled = selectedApp.disabled, 19 | onClick = onAppClick 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /screens/editfavorites/src/main/kotlin/dev/mslalith/focuslauncher/screens/editfavorites/utils/TestTags.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.editfavorites.utils 2 | 3 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 4 | 5 | @IgnoreInKoverReport 6 | internal object TestTags { 7 | const val TAG_FAVORITES_LIST_ITEM = "tag_favorites_list_item" 8 | const val TAG_FAVORITES_LIST = "tag_favorites_list" 9 | const val TAG_CLEAR_FAVORITES_FAB = "tag_clear_favorites_fab" 10 | const val TAG_TOGGLE_HIDDEN_APPS = "tag_toggle_hidden_apps" 11 | } 12 | -------------------------------------------------------------------------------- /screens/hideapps/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /screens/hideapps/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.screens.hideapps" 9 | } 10 | 11 | dependencies { 12 | implementation(libs.kotlinx.collections.immutable) 13 | } 14 | -------------------------------------------------------------------------------- /screens/hideapps/src/main/kotlin/dev/mslalith/focuslauncher/screens/hideapps/HideAppsContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.hideapps 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.model.app.App 6 | import dev.mslalith.focuslauncher.core.model.app.SelectedHiddenApp 7 | import kotlinx.collections.immutable.ImmutableList 8 | 9 | data class HideAppsState( 10 | val hiddenApps: ImmutableList, 11 | val eventSink: (HideAppsUiEvent) -> Unit 12 | ) : CircuitUiState 13 | 14 | sealed interface HideAppsUiEvent : CircuitUiEvent { 15 | data class AddToHiddenApps(val app: App) : HideAppsUiEvent 16 | data class RemoveFromHiddenApps(val app: App) : HideAppsUiEvent 17 | data object ClearHiddenApps : HideAppsUiEvent 18 | 19 | data class RemoveFromFavorites(val app: App) : HideAppsUiEvent 20 | data object GoBack : HideAppsUiEvent 21 | } 22 | -------------------------------------------------------------------------------- /screens/hideapps/src/main/kotlin/dev/mslalith/focuslauncher/screens/hideapps/utils/TestTags.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.hideapps.utils 2 | 3 | import dev.mslalith.focuslauncher.core.lint.kover.IgnoreInKoverReport 4 | 5 | @IgnoreInKoverReport 6 | internal object TestTags { 7 | const val TAG_HIDDEN_APPS_LIST_ITEM = "tag_hidden_apps_list_item" 8 | const val TAG_HIDDEN_APPS_LIST = "tag_hidden_apps_list" 9 | const val TAG_CLEAR_HIDDEN_APPS_FAB = "tag_clear_hidden_apps_fab" 10 | } 11 | -------------------------------------------------------------------------------- /screens/iconpack/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /screens/iconpack/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.screens.iconpack" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.feature.appdrawerpage) 13 | 14 | implementation(libs.kotlinx.collections.immutable) 15 | } 16 | -------------------------------------------------------------------------------- /screens/iconpack/src/main/kotlin/dev/mslalith/focuslauncher/screens/iconpack/IconPackContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.iconpack 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.core.common.model.LoadingState 6 | import dev.mslalith.focuslauncher.core.model.IconPackType 7 | import dev.mslalith.focuslauncher.core.model.app.AppWithIcon 8 | import dev.mslalith.focuslauncher.core.model.appdrawer.AppDrawerItem 9 | import kotlinx.collections.immutable.ImmutableList 10 | 11 | data class IconPackState( 12 | val allApps: LoadingState>, 13 | val iconPacks: ImmutableList, 14 | val iconPackType: IconPackType?, 15 | val canSave: Boolean, 16 | val eventSink: (IconPackUiEvent) -> Unit 17 | ) : CircuitUiState 18 | 19 | sealed interface IconPackUiEvent : CircuitUiEvent { 20 | data object GoBack : IconPackUiEvent 21 | data object SaveIconPack : IconPackUiEvent 22 | data class UpdateSelectedIconPackApp(val iconPackType: IconPackType) : IconPackUiEvent 23 | } 24 | -------------------------------------------------------------------------------- /screens/launcher/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /screens/launcher/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("focuslauncher.android.feature") 3 | id("focuslauncher.screen.new") 4 | id("focuslauncher.android.library.compose.testing") 5 | } 6 | 7 | android { 8 | namespace = "dev.mslalith.focuslauncher.screens.launcher" 9 | } 10 | 11 | dependencies { 12 | implementation(projects.feature.homepage) 13 | implementation(projects.feature.settingspage) 14 | implementation(projects.feature.appdrawerpage) 15 | } 16 | -------------------------------------------------------------------------------- /screens/launcher/src/main/kotlin/dev/mslalith/focuslauncher/screens/launcher/LauncherContract.kt: -------------------------------------------------------------------------------- 1 | package dev.mslalith.focuslauncher.screens.launcher 2 | 3 | import com.slack.circuit.runtime.CircuitUiEvent 4 | import com.slack.circuit.runtime.CircuitUiState 5 | import dev.mslalith.focuslauncher.feature.appdrawerpage.AppDrawerPageState 6 | import dev.mslalith.focuslauncher.feature.homepage.HomePageState 7 | import dev.mslalith.focuslauncher.feature.settingspage.SettingsPageState 8 | 9 | data class LauncherState( 10 | val settingsPageState: SettingsPageState, 11 | val homePageState: HomePageState, 12 | val appDrawerPageState: AppDrawerPageState 13 | ) : CircuitUiState 14 | 15 | sealed interface LauncherUiEvent : CircuitUiEvent 16 | -------------------------------------------------------------------------------- /scripts/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Running Git pre-push hook" 4 | 5 | echo "Running static analysis" 6 | ./gradlew detekt --daemon 7 | 8 | STATUS=$? 9 | 10 | [ $STATUS -ne 0 ] && exit 1 11 | exit 0 12 | --------------------------------------------------------------------------------