├── .gitignore ├── CREDITS.md ├── Convention.md ├── FAQ.md ├── LICENSE.md ├── README.md ├── README_EN.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── singularity_code │ │ └── basearchitecture │ │ └── ExampleInstrumentedTest.kt │ ├── main │ └── AndroidManifest.xml │ └── test │ └── java │ └── com │ └── singularity_code │ └── basearchitecture │ └── ExampleUnitTest.kt ├── arsitektur-overview.pdf ├── arsitektur-overview.svg ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── com │ └── singularity_code │ └── plugins │ └── Extension.kt ├── control ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── igniter │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── control │ │ │ └── igniter │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── control │ │ │ └── igniter │ │ │ ├── Application.kt │ │ │ └── Injector.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── control │ │ └── igniter │ │ └── ExampleUnitTest.kt ├── proguard-rules.pro ├── provider │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── control │ │ │ └── provider │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── control │ │ │ └── provider │ │ │ └── sample │ │ │ ├── mmdexample │ │ │ └── MMDExampleSpace.kt │ │ │ ├── pokemon │ │ │ ├── PokemonSpace.kt │ │ │ ├── PokemonSpaceStation.kt │ │ │ ├── model │ │ │ │ └── PokemonGem.kt │ │ │ └── payload │ │ │ │ └── GetPokemonByIdSPLD.kt │ │ │ └── todolist │ │ │ └── TodoSpace.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── control │ │ └── provider │ │ └── ExampleUnitTest.kt └── src │ └── main │ └── AndroidManifest.xml ├── core ├── .gitignore ├── build.gradle.kts ├── common │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── core │ │ │ └── common │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── singularity_code │ │ │ │ └── core │ │ │ │ └── common │ │ │ │ ├── DI.kt │ │ │ │ ├── model │ │ │ │ ├── Data.kt │ │ │ │ ├── NOTHING.kt │ │ │ │ ├── Toast.kt │ │ │ │ └── VmError.kt │ │ │ │ ├── pattern │ │ │ │ ├── Bootstrap.kt │ │ │ │ ├── ClearAble.kt │ │ │ │ ├── Component.kt │ │ │ │ ├── JsonConvertible.kt │ │ │ │ ├── Payload.kt │ │ │ │ ├── Readme.md │ │ │ │ ├── State.kt │ │ │ │ ├── activity │ │ │ │ │ ├── ActivityResultEmitter.kt │ │ │ │ │ ├── BaseActivity.kt │ │ │ │ │ ├── BaseActivityAbs.kt │ │ │ │ │ └── LauncherAbs.kt │ │ │ │ ├── application │ │ │ │ │ └── BaseApplication.kt │ │ │ │ ├── navigation │ │ │ │ │ ├── BaseNavigation.kt │ │ │ │ │ ├── Portal.kt │ │ │ │ │ ├── Space.kt │ │ │ │ │ └── SpaceStation.kt │ │ │ │ └── viewmodel │ │ │ │ │ ├── BaseViewModel.kt │ │ │ │ │ └── BaseViewModelAbs.kt │ │ │ │ └── util │ │ │ │ ├── Aliases.kt │ │ │ │ ├── Apollo.kt │ │ │ │ ├── BaseViewModel.kt │ │ │ │ ├── Coroutine.kt │ │ │ │ ├── Date.kt │ │ │ │ ├── Json.kt │ │ │ │ ├── Launcher.kt │ │ │ │ ├── List.kt │ │ │ │ ├── Log.kt │ │ │ │ ├── Long.kt │ │ │ │ ├── Null.kt │ │ │ │ ├── Readme.md │ │ │ │ ├── State.kt │ │ │ │ ├── Toast.kt │ │ │ │ ├── application │ │ │ │ └── ApplicationContext.kt │ │ │ │ ├── injection │ │ │ │ └── Koin.kt │ │ │ │ ├── network │ │ │ │ └── RetrofitUtil.kt │ │ │ │ └── viewmodel │ │ │ │ └── RequestState.kt │ │ └── res │ │ │ ├── values │ │ │ └── strings.xml │ │ │ └── xml │ │ │ ├── backup_rules.xml │ │ │ └── data_extraction_rules.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── core │ │ └── common │ │ └── ExampleUnitTest.kt ├── consumer-rules.pro ├── network │ ├── .gitignore │ ├── CMakeLists.txt │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── core │ │ │ └── network │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── cpp │ │ │ ├── dev-url.cpp │ │ │ ├── prod-url.cpp │ │ │ └── stage-url.cpp │ │ ├── graphql │ │ │ ├── GetPokemonById.graphql │ │ │ ├── GetPokemonList.graphql │ │ │ └── schema.json │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── core │ │ │ └── network │ │ │ ├── DI.kt │ │ │ ├── data │ │ │ └── Secured.kt │ │ │ └── util │ │ │ ├── Apollo.kt │ │ │ ├── Okhttp.kt │ │ │ ├── Secured.kt │ │ │ └── interceptor │ │ │ └── ChuckerInterceptor.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── core │ │ └── network │ │ └── ExampleUnitTest.kt ├── proguard-rules.pro ├── src │ └── main │ │ └── AndroidManifest.xml └── ui │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── singularity_code │ │ └── core │ │ └── ui │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── singularity_code │ │ │ └── core │ │ │ └── ui │ │ │ ├── DI.kt │ │ │ └── util │ │ │ ├── ContentThemeWrapper.kt │ │ │ ├── Dimen.kt │ │ │ └── material3 │ │ │ ├── Color.kt │ │ │ ├── Dimens.kt │ │ │ ├── FontFamily.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ ├── ic_singularity_logo.xml │ │ └── ic_singularity_logo_circle.xml │ │ ├── font │ │ └── inter_regular.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ └── values │ │ ├── colors.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── singularity_code │ └── core │ └── ui │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keystore.properties.example ├── local.properties ├── modsample ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── mmdexample │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── modsample │ │ │ └── mmdexample │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── mmdexample │ │ │ ├── DI.kt │ │ │ └── ui │ │ │ ├── DI.kt │ │ │ ├── activity │ │ │ ├── MMDExampleActivity.kt │ │ │ └── MMDExampleActivitySpace.kt │ │ │ └── screen │ │ │ ├── home │ │ │ └── HomeScreen.kt │ │ │ └── viewmodel │ │ │ ├── HomeViewModel.kt │ │ │ ├── HomeViewModelUseCase.kt │ │ │ └── usecase │ │ │ └── PokemonViewModelUseCase.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── modsample │ │ └── mmdexample │ │ └── ExampleUnitTest.kt ├── pokemon │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── modsample │ │ │ └── pokemon │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── modsample │ │ │ └── pokemon │ │ │ ├── DI.kt │ │ │ ├── data │ │ │ ├── PokemonRepository.kt │ │ │ ├── payload │ │ │ │ ├── GetPokemonByIdPld.kt │ │ │ │ └── GetPokemonListPld.kt │ │ │ └── src │ │ │ │ └── web │ │ │ │ └── WebApi.kt │ │ │ ├── station │ │ │ ├── DI.kt │ │ │ └── SpaceStation.kt │ │ │ └── ui │ │ │ ├── DI.kt │ │ │ ├── activity │ │ │ ├── PokemonActivity.kt │ │ │ └── PokemonActivitySpace.kt │ │ │ └── screen │ │ │ └── home │ │ │ ├── HomeScreen.kt │ │ │ └── viewmodel │ │ │ ├── HomeViewModel.kt │ │ │ └── HomeViewModelUseCase.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── modsample │ │ └── pokemon │ │ └── ExampleUnitTest.kt ├── proguard-rules.pro ├── splash │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── modsample │ │ │ └── splash │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── singularity_code │ │ │ └── modsample │ │ │ └── splash │ │ │ ├── DI.kt │ │ │ └── ui │ │ │ ├── DI.kt │ │ │ ├── activity │ │ │ ├── SplashActivity.kt │ │ │ └── SplashActivityUseCase.kt │ │ │ ├── navigation │ │ │ └── SplashPortal.kt │ │ │ └── screen │ │ │ └── splash │ │ │ └── SplashScreen.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── singularity_code │ │ └── modsample │ │ └── splash │ │ └── ExampleUnitTest.kt ├── src │ └── main │ │ └── AndroidManifest.xml └── todolist │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── singularity_code │ │ └── modsample │ │ └── todolist │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── singularity_code │ │ └── modsample │ │ └── todolist │ │ ├── DI.kt │ │ ├── data │ │ ├── TodoRepository.kt │ │ ├── model │ │ │ └── TodoMdl.kt │ │ ├── payload │ │ │ ├── GetTodoByIdPld.kt │ │ │ └── GetTodoListPld.kt │ │ └── src │ │ │ └── web │ │ │ └── WebApi.kt │ │ └── ui │ │ ├── DI.kt │ │ ├── activity │ │ ├── TodoActivity.kt │ │ └── TodoActivitySpace.kt │ │ └── screen │ │ ├── TodoScreen.kt │ │ └── viewmodel │ │ ├── TodoViewModel.kt │ │ └── TodoViewModelUseCase.kt │ └── test │ └── java │ └── com │ └── singularity_code │ └── modsample │ └── todolist │ └── ExampleUnitTest.kt └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle/ 2 | **/.gradle/** 3 | **/build/** 4 | /keystore.properties 5 | /testsignature 6 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | ## Joseph Sanjaya 2 | **Navigation Concept and Terminology** 3 | 4 | The original concept of navigation im currently used belongs to my friend **[Joseph Sanjaya](https://www.linkedin.com/in/josephsanjaya/)**. 5 | 6 | Special thanks to my friend for his great idea for navigation Concept and Terminology. Joseph is an expert android software developer and expert software designer. 7 | 8 | Meet **Joseph** : 9 | 1. [LinkedIn @josephsanjaya](https://www.linkedin.com/in/josephsanjaya/) 10 | 2. [Github @JosephSanjaya](https://github.com/JosephSanjaya) 11 | 3. [Devto @josephsanjaya](https://dev.to/josephsanjaya) 12 | 4. [Twitter @sanjayajosep](https://twitter.com/sanjayajosep) 13 | 5. [Stack overflow @joseph-sanjaya](https://stackoverflow.com/users/11767813/joseph-sanjaya) 14 | 6. Discord: Joseph Sanjaya#0330 15 | -------------------------------------------------------------------------------- /Convention.md: -------------------------------------------------------------------------------- 1 | 2 | # Code Convention 3 | 4 | ## 1. Resource Naming 5 | For resource naming use snake_case 6 | 7 | |Resource| Type | Format | 8 | | ------ | ------ | ------ | 9 | |Drawable| Icon Drawable | ic_module_purpose_desc | 10 | || Image Drawable | img_module_purpose_desc | 11 | || Selector Drawable | selector_module_purpose_desc | 12 | || Other Drawable | other_module_purpose_desc | 13 | 14 | ## 2. Strings 15 | For resource naming use snake_case 16 | 17 | |Type| Format | Spec | Desc| 18 | | ------ | ------ | ------ | ------ | 19 | |Title| title_module_purpose_desc | Capital Each Word | Use for title | 20 | |Label| label_module_purpose_desc | Capital First Word | Use for any purpose | 21 | |Action| action_module_purpose_desc | Capital Each Word | Use for action like button | 22 | |Other| other_module_purpose_desc | Freestyle | Use for something else | 23 | 24 | ## 3. Class Naming 25 | Use PascalCase. 26 | 27 | - For **Payload** class add **Pld** as it's suffix. 28 | ex: **SomePld** 29 | - For **Model** class add **Mdl** as it's suffix. 30 | ex: **SomeMdl**. 31 | For GraphQl Model just forget about this, let graphql decide. It is the boss. 32 | 33 | ## 4. Variable Naming 34 | Use camelCase 35 | 36 | ## 5. Compose-able Naming 37 | Use PascalCase 38 | 39 | ## 6 Constant Naming 40 | Use CAPITAL_SNAKE 41 | 42 | ## 7. Enum Member Naming 43 | Use CAPITAL_SNAKE. 44 | ``` 45 | enum SomeEnumClass { 46 | FIRST_MEMBER, SECOND_MEMBER, ETC 47 | } 48 | ``` -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | ### Why Koin not Hilt 2 | According to my expert friend, ada satu alasan bagus menggunakan Hilt, yaitu compilation check. Dimana dependensi akan di chek ketika compile time. Ini bagus jika kita bekerja dengan tim besar, mengurangi effor QA untuk melakukan pengecekan. 3 | 4 | Tapi dalam proyek ini lebih ke preferensi. Saya kurang suka hilt karena dia meng-interfensi objek yang akan di inject dan memberi sedikit pattern lebih pada objek yang menggunakan fitur ini. Yes, saya kurang suka menggunakan anotasi ketika melakukan injeksi. Salah satu contohnya pattern ini memaksa kita menggunakan late init, artinya proyek tidak hanya depend ke Hilt tapi pattern dari proyek juga akan tightly coupled ke Hilt itu sendiri. 5 | 6 | Contoh: 7 | ``` 8 | @Provides 9 | fun provideMusicDB(@ApplicationContext context: Context): MusicDatabase { 10 | return Room.databaseBuilder ( 11 | context, 12 | MusicDatabase::class.java, "music.db" 13 | ).build() 14 | } 15 | ``` 16 | 17 | Bagi saya it is like yikes. Menurut saya contoh di atas tidak lagi bisa di sebut sebagai pure function. 18 | 19 | Contoh lain, dengan Hilt anda perlu melakukan ini: 20 | ``` 21 | @Inject late init var module: Module 22 | ``` 23 | 24 | Sementara dengan koin cukup seperti ini: 25 | ``` 26 | private val module: Module = get() 27 | ``` 28 | Bagi saya adalah pendekatan yang jauh lebih baik. 29 | 30 | Lalu bagaimana jika dependensi lupa di declare? We go fight each other. We don't want peace. 31 | 32 | ### Build Flavor 33 | Jika anda ingin mencoba build flavor staging dan release, anda perlu membuat sebuah keystore / key signature. 34 | Langkah petunjuk dapat anda lihat pada: [keystore.properties.example](keystore.properties.example) 35 | 36 | ### Slow Build 37 | Proyek ini menggunakan precompile script sehingga anda mungkin akan merasakan build time yang sedikit lebih lama. 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Stefanus Ayudha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | This readme comes with another languages : 2 | - [English (EN)](README_EN.md) 3 | - [Bahasa Indonesia (ID)](README.md) 4 | 5 | PREVIEW ONLY 6 | Main fitures are still in cooking and testing, such : 7 | - Modification Safe 8 | 9 | Be sure to follow this project to get notification about project updates. 10 | 11 | # THANK YOU 12 | First i want to thank you and appreciate all the contributors of this project; [Contributors](CREDITS.md). 13 | 14 | - **Joseph Sanjaya** (meet: [joseph](https://github.com/JosephSanjaya)). 15 | The original Idea of the navigation's concept and terminology of this codebase. 16 | 17 | # FREE TO USE 18 | You can use this codebase as your project base and modify this codebase for your project purpose. I create this codebase so hopefully it can helps anybody to start their project, especially a big scaled project. I thank you for anybody that is using this codebase as their project base, since by using this project, you are contributing by helping me testing this codebase. I hope by more user using this codebase, will give me input feedback or advice to help me improve this codebase. To make this codebase better and robust. I will also be pleased to receive your advice, idea or contribution for this project. 19 | 20 | # Welcome to My Codebase Design! 21 | To measure something we need a base line and a measurement tool, so for a codebase. The base line is **Dependency Flow** and **Runtime Flow**, and the measurement tools are **Software Development Principles**. How robust is it by how many **Software Development Principles** applied to it. Other than that are preferences. 22 | 23 | Here we will "**Back to Basic**" by focus in **Dependency Flow** and **Runtime Flow**. 24 | 25 | I have a simple principle; "If it's hard, than it's wrong". So we will make it easier my introducing new modules in the gradle project called **Provider** and **Igniter**. 26 | 27 | Main ideas of this project are: 28 | 1. Completely remove Horizontal dependency by providing new modules called **Provider** and **Igniter**. 29 | 2. Remove states in Data Layers by optimizing functional pattern's approach in Data Layers. 30 | 3. Localize side effects. We have to limit modules that can do side effect and if possible completely remove it. 31 | 32 | # Project's Architecture 33 | The project's architecture covers all our gradle project's modules. Not everything we can do with the gradle but having exceptions is bad if we are not setting a limit to it. So we will allow for only two exceptions in this module's architecture. 34 | 1. App Module is part of Igniter module. So it can behaves like Igniter module, but it can't provides any modules just like the Igniter module. 35 | for example; Igniter module, inject all runtime modules to Application class, mean while App module inject or declare Activity to the manifest file. 36 | 2. Straight dependency to the Core Module. Because of core module's components are used to be small in sizes and so many. It will be to hard to Inversely inject it. So we will just depend to it straightly **through the Provider Module**. 37 | 38 | 39 | **Circular Dependency** happens mainly because we allow horizontal dependency. So in this architecture design, we won't allow any horizontal dependency. 40 | 1. A module cannot depend to another module except the Provider module. 41 | 2. A module, should not knowing the existence of another Modules except the Provider module. 42 | 43 | ## Provider 44 | As how it is called a "Provider module", it means to provide modules according to the contract that has been declared in the Provider Module. The modules it provides can be **APIs**, **Activities**, **Widgets** and else. 45 | 46 | ### Runtime Flow 47 | ```mermaid 48 | graph LR 49 | A[Module 1] -- payload --> B((Provider)) -- payload --> C[Module 2] 50 | ``` 51 | 52 | Any module can depend to another module by the contract that has already declare in the Provider Module. Of course the module it self should first be registered by the **Igniter Module**. 53 | 54 | ### Dependency Flow 55 | ```mermaid 56 | graph LR 57 | A[Module 1] --> B((Provider)) 58 | C[Module 2] --> B 59 | D[Module 3] --> B 60 | B --> E{{Core}} 61 | ``` 62 | 63 | All modules that is categorized as Runtime Modules can only depend to Provider Module. Also the **Provider Module** is the only module that is directly depends to the **Core Module**. So every module that depends to the **Provider Module** can access the **Core Module** via the **Provider Module**. 64 | This straight dependency of the **Provider Module** to the **Core Module** is an exception to the Dependency Inversion principle. Since Core Module's components are used to be so many and smalls, it will be easier to directly depends to them rather than inversely injecting them. 65 | 66 | ## Igniter 67 | Igniter is a fuse or a wick. This module responsible to inject all module to the Application class, to tie everything together. That is why this module should depends to everything and then put everything together, injects them to the Application class. 68 | 69 | **Application** should be provided by the Igniter, and the Igniter shall inject everything together into a wick in the Application class. 70 | 71 | ## Architecture Overview 72 | For architecture overview see: [arsitektur-overview.pdf](arsitektur-overview.pdf) file. 73 | 74 | # Internal Module Arsitektur 75 | We have MVVM, Clean Architecture, and SOLID but should and app be that much complicated? As a front end developer, i never seen such a very complicated case that i think is worth the effort. 76 | 77 | Front-end application only consist of 2 things, there are **UI** and **Data**. 78 | 79 | ## UI 80 | UI is a high level module, no doubt. It is full with side-effects, lifecycles, configuration changes and else. This module is indeed complicated. So for this one SOLID principle and Dependency Injection will so much helping. Nothing special that i want to say but **ONE THING**: 81 | > # THINK STATELESS 82 | Make everything state less if possible. Never make something that have active internal state in it. 83 | 84 | **Note**: Even though UI is full with side effects, we cannot just believe it. But if possible we have to remove it completely. For example; in imperative android's programming, i used to allow side effects happens only inside the Controller Class (Fragment or Activity). 85 | 1. It cannot have side effects anywhere else but inside the controller class. 86 | Even though viewmodel and adapter are also UI's module but since they are not consider a controller class, they should not be allowed to have side effects. 87 | 2. Class with side effects cannot doing "**Cross Responsibility Side Effect**" 88 | For example, if there is a fragment that implements the contract A and B. The overriden functions of each contract cannot sharing side effects. 89 | If for some reason the overriden function of the contract A should save some value in a global variable, the overrided methods of the contract B should never touch this variable, otherwise it will consider a **Cross Responsibility Side Effect**. If the overriden function of the contract B need the value from the contract A associated instance's function, the contract A should provides the value in some way and obey the Dependency Inversion Principle. 90 | 91 | ## Data 92 | Here is where the interesting happens. Data layer don't need side effect, it cares not to the lifecycle and configuration changes such like rotation, theme changing and else. This module is so simple, so why won't we make it simpler? 93 | 94 | In this project example, Data Modules are not obeying the SOLID principles. But we will maximize the **Functional Pattern** approach as far as possible. I still found some difficulties applying Kotlin Arrow library for now but it's still obey the basic of the Functional Pattern. I do need your help on this. 95 | 1. Data Module should not have states. It should only concern about what data is requested and deliver it or give an error by Throwable or Either - Left , which ever you want. 96 | 2. Data Module's job is only to provides data from the data source. How the data source would behave is not the concern of the Data Module. 97 | 3. Methods or Interfaces in Data Module must be Pure Functions or Suspended Pure Functions. 98 | 4. Since they no longer have states, rather than using dependency inversion we will use monoid chain instead, it is way more simpler. 99 | 100 | ## There is no Domain Layers 101 | For the professionals, this might be consider a taboo. But i do this in purpose as an experiment, since this approach is one of the purpose why i built this code base. But don't be worried, feel free to do what you want. 102 | 103 | I removed the domain layers by purpose to maximize the **Pure Function** approach in the Data Layers. 104 | 105 | ## Pure Function Approach 106 | 1. Every functions in Data Layer should be **Pure Functions** or **Suspended Pure Functions**. 107 | 2. Pure Function should obey these criteria: 108 | >1. Immutable Argument. 109 | >2. Always return result not reference (not relevant to kotlin/java but good to know it). 110 | >3. Same output for the same input. 111 | >4. No side effects. 112 | >5. No states. 113 | >6. Function duty is only change an input into an output. For example; an Integer into a String, or a Payload into a Data, or Error if the process cannot be fulfilled. 114 | 4. This approach requires the same output for the same given input, means that if we expect a different output, we should provide different input. Example case; if there is changes in the business logic; In the simple way, for every different business logic we shall provides different payload. Or in another way, we don't have such concept "a business process change", rather we consider it as "a new business process". 115 | We don't change the business process, but we scale the business process by providing new Pure Function and new Payload for every new business process. 116 | 4. No more Interface Segregation. Imagine a factory object that implements so many UseCases, so it is become very bulky, while all we need is only 1 API. It is so unefficient to build the whole object only to use one API. This is always be a dilema between good practices and memory management. But with Pure Function Approach, there is no more worries. For any problem, all you need is just a (new/existing) Function. 117 | 118 | # Contributing 119 | I would love to have contributors, and i do open this project for contributor. But for now i still making the procedure's files like Gitflow and else, so if you are interesting to make contribution, you can DM me. You can find me at the Author section bellow. 120 | 121 | You can see the project progress [here](https://github.com/users/stefanusayudha/projects/2). 122 | 123 | # Meet the Author 124 | I'm steve, a programmer for fun. You can find me at these links bellow: 125 | 126 | 1. [Instagram @stefanus_ayudha](https://www.instagram.com/stefanus_ayudha/) 127 | 2. [Telegram @stefanus_ayudha](https://t.me/stefanus_ayudha) 128 | 3. [Email @stefanus.ayudha](mailto:stefanus.ayudha@gmail.com) 129 | 4. [Linkedin @stefanus_ayudha](https://www.linkedin.com/in/stefanus-ayudha-447a98b5/) 130 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release/app-release.apk 3 | /release/output-metadata.json 4 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.singularity_code.plugins.defaultAppConfig 2 | import com.singularity_code.plugins.getKeyStoreProperties 3 | 4 | plugins { 5 | id(libs.plugins.android.application.get().pluginId) 6 | id(libs.plugins.kotlin.android.get().pluginId) 7 | } 8 | 9 | // Load keystore 10 | val keystoreProperties = getKeyStoreProperties( 11 | "${rootDir.absolutePath}/keystore.properties" 12 | ) 13 | 14 | defaultAppConfig( 15 | appId = "com.singularity_code.basearchitecture", 16 | versionCode = 1, 17 | versionName = "1.0", 18 | keystoreProperties = keystoreProperties 19 | ) 20 | 21 | dependencies { 22 | api(project(":control:igniter")) 23 | } 24 | -------------------------------------------------------------------------------- /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.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/singularity_code/basearchitecture/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.basearchitecture 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.singularity_code.basearchitecture", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/com/singularity_code/basearchitecture/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.basearchitecture 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /arsitektur-overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/arsitektur-overview.pdf -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | dependencies { 4 | classpath("com.android.tools.build:gradle:${libs.versions.gradle.plugin.get()}") 5 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlin.get()}") 6 | } 7 | extra.apply { 8 | // set("variableName", "value") 9 | } 10 | } 11 | 12 | plugins { 13 | id (libs.plugins.android.application.get().pluginId) apply false 14 | id (libs.plugins.android.library.get().pluginId) apply false 15 | id (libs.plugins.kotlin.android.get().pluginId) apply false 16 | } 17 | 18 | tasks.register("clean", Delete::class) { 19 | delete(rootProject.buildDir) 20 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | `kotlin-dsl-precompiled-script-plugins` 4 | } 5 | 6 | gradlePlugin { 7 | plugins { 8 | register("common-binary-plugin") { 9 | id = "common-binary-plugin" 10 | implementationClass = "com.singularity_code.plugins.CommonBinaryPlugin" 11 | } 12 | } 13 | } 14 | 15 | buildscript { 16 | 17 | repositories { 18 | gradlePluginPortal() 19 | google() 20 | mavenCentral() 21 | } 22 | 23 | dependencies { 24 | classpath("com.android.tools.build:gradle:7.2.2") 25 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") 26 | } 27 | } 28 | 29 | repositories { 30 | gradlePluginPortal() 31 | google() 32 | mavenCentral() 33 | } 34 | 35 | /** 36 | * let gradle define it 37 | * 'val compileKotlin: KotlinCompile by tasks 38 | * compileKotlin.kotlinOptions { 39 | * languageVersion = "1.3" 40 | * }' 41 | **/ 42 | 43 | dependencies { 44 | implementation("com.android.tools.build:gradle:7.2.2" ) 45 | implementation("com.android.tools.build:gradle-api:7.2.2") 46 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10") 47 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") 48 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/com/singularity_code/plugins/Extension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.plugins 6 | 7 | import com.android.build.gradle.BaseExtension 8 | import org.gradle.api.JavaVersion 9 | import org.gradle.api.Project 10 | import java.io.FileInputStream 11 | import java.util.* 12 | 13 | /** 14 | * ## Default Config 15 | */ 16 | const val MIN_SDK = 28 17 | const val TARGET_SDK = 32 18 | const val COMPILE_SDK = 33 19 | 20 | /** 21 | * ## Util 22 | */ 23 | val Project.android: BaseExtension 24 | get() = extensions.findByName("android") as? BaseExtension 25 | ?: error("Project '$name' is not an Android module") 26 | 27 | /** 28 | * Use to define application configuration 29 | */ 30 | fun Project.defaultAppConfig( 31 | appId: String, 32 | versionCode: Int = 1, 33 | versionName: String = "1.0", 34 | keystoreProperties: Properties? = null, 35 | minSdk: Int = MIN_SDK, 36 | targetSdk: Int = TARGET_SDK, 37 | compileSdk: Int = COMPILE_SDK, 38 | ) { 39 | defaultConfigBuilder( 40 | appId = appId, 41 | verCode = versionCode, 42 | verName = versionName, 43 | keystoreProperties = keystoreProperties, 44 | minSdk = minSdk, 45 | targetSdk = targetSdk, 46 | compileSdk = compileSdk 47 | ) 48 | } 49 | 50 | /** 51 | * Use to define library configuration 52 | */ 53 | fun Project.defaultLibraryConfig( 54 | versionCode: Int = 1, 55 | versionName: String = "1.0", 56 | keystoreProperties: Properties? = null, 57 | minSdk: Int = MIN_SDK, 58 | targetSdk: Int = TARGET_SDK, 59 | compileSdk: Int = COMPILE_SDK, 60 | ) { 61 | defaultConfigBuilder( 62 | verCode = versionCode, 63 | verName = versionName, 64 | keystoreProperties = keystoreProperties, 65 | minSdk = minSdk, 66 | targetSdk = targetSdk, 67 | compileSdk = compileSdk 68 | ) 69 | } 70 | 71 | private fun Project.defaultConfigBuilder( 72 | appId: String? = null, 73 | verCode: Int, 74 | verName: String, 75 | keystoreProperties: Properties? = null, 76 | minSdk: Int, 77 | targetSdk: Int, 78 | compileSdk: Int, 79 | ) { 80 | android.apply { 81 | compileSdkVersion(compileSdk) 82 | defaultConfig { 83 | if (appId.isNullOrBlank().not()) 84 | applicationId = appId 85 | 86 | this.minSdk = minSdk 87 | this.targetSdk = targetSdk 88 | versionCode = verCode 89 | versionName = verName 90 | 91 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 92 | vectorDrawables { 93 | useSupportLibrary = true 94 | } 95 | } 96 | 97 | if (keystoreProperties != null) 98 | signingConfigs { 99 | create("release") { 100 | storeFile = file(keystoreProperties["storeFile"] as String) 101 | storePassword = keystoreProperties["storePassword"] as String 102 | keyAlias = keystoreProperties["keyAlias"] as String 103 | keyPassword = keystoreProperties["keyPassword"] as String 104 | } 105 | } 106 | 107 | buildTypes { 108 | getByName("debug") { 109 | isMinifyEnabled = false 110 | isDebuggable = true 111 | proguardFiles( 112 | getDefaultProguardFile("proguard-android-optimize.txt"), 113 | "proguard-rules.pro" 114 | ) 115 | } 116 | 117 | if (keystoreProperties != null) 118 | create("staging") { 119 | isMinifyEnabled = true 120 | isDebuggable = true 121 | signingConfig = signingConfigs.getByName("release") 122 | proguardFiles( 123 | getDefaultProguardFile("proguard-android-optimize.txt"), 124 | "proguard-rules.pro" 125 | ) 126 | } 127 | 128 | if (keystoreProperties != null) 129 | getByName("release") { 130 | isMinifyEnabled = true 131 | isDebuggable = false 132 | signingConfig = signingConfigs.getByName("release") 133 | proguardFiles( 134 | getDefaultProguardFile("proguard-android-optimize.txt"), 135 | "proguard-rules.pro" 136 | ) 137 | } 138 | } 139 | compileOptions { 140 | sourceCompatibility = JavaVersion.VERSION_1_8 141 | targetCompatibility = JavaVersion.VERSION_1_8 142 | } 143 | packagingOptions { 144 | resources { 145 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 146 | } 147 | } 148 | lintOptions { 149 | disable += "Instantiatable" 150 | } 151 | } 152 | } 153 | 154 | fun Project.getKeyStoreProperties( 155 | path: String 156 | ): Properties? = Properties().let { 157 | 158 | val fileLoaded = kotlin.runCatching { 159 | it.load( 160 | FileInputStream( 161 | file(path) 162 | ) 163 | ) 164 | }.isSuccess 165 | 166 | if (fileLoaded) it 167 | else null 168 | } 169 | -------------------------------------------------------------------------------- /control/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /control/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.BaseExtension 2 | import com.singularity_code.plugins.defaultLibraryConfig 3 | import com.singularity_code.plugins.getKeyStoreProperties 4 | 5 | plugins { 6 | id("java-platform") 7 | } 8 | 9 | group = "android" 10 | version = "1.0.0" 11 | 12 | // Load keystore 13 | val keystoreProperties = getKeyStoreProperties( 14 | "${rootDir.absolutePath}/keystore.properties" 15 | ) 16 | 17 | subprojects { 18 | apply(plugin = "com.android.library") 19 | apply(plugin = "org.jetbrains.kotlin.android") 20 | apply(plugin = "kotlin-android") 21 | apply(plugin = "kotlin-parcelize") 22 | apply(plugin = "kotlin-kapt") 23 | 24 | plugins.withType(BasePlugin::class.java).configureEach { 25 | configure { 26 | defaultLibraryConfig( 27 | versionCode = 1, 28 | versionName = "1.0", 29 | keystoreProperties = keystoreProperties 30 | ) 31 | } 32 | 33 | } 34 | 35 | } 36 | 37 | dependencies { 38 | constraints { 39 | api(project(":control:provider")) 40 | } 41 | } -------------------------------------------------------------------------------- /control/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/control/consumer-rules.pro -------------------------------------------------------------------------------- /control/igniter/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /control/igniter/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | api(project(":core:common")) 3 | api(project(":core:ui")) 4 | api(project(":core:network")) 5 | 6 | api(project(":control:provider")) 7 | 8 | api(project(":modsample:splash")) 9 | api(project(":modsample:pokemon")) 10 | api(project(":modsample:todolist")) 11 | api(project(":modsample:mmdexample")) 12 | } -------------------------------------------------------------------------------- /control/igniter/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/control/igniter/consumer-rules.pro -------------------------------------------------------------------------------- /control/igniter/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 -------------------------------------------------------------------------------- /control/igniter/src/androidTest/java/com/singularity_code/control/igniter/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.igniter 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.singularity_code.control.igniter.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /control/igniter/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 16 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 35 | 38 | 41 | 42 | -------------------------------------------------------------------------------- /control/igniter/src/main/java/com/singularity_code/control/igniter/Application.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.igniter 2 | 3 | import com.singularity_code.core.common.pattern.application.BaseApplication 4 | 5 | class Application : BaseApplication() { 6 | override val modules = allModules 7 | } -------------------------------------------------------------------------------- /control/igniter/src/main/java/com/singularity_code/control/igniter/Injector.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.igniter 2 | 3 | import com.singularity_code.core.common.coreMainModules 4 | import com.singularity_code.core.network.coreNetworkModules 5 | import com.singularity_code.core.ui.coreUIModules 6 | import com.singularity_code.mmdexample.modMMDSampleModules 7 | import com.singularity_code.modsample.pokemon.modPokemonModules 8 | import com.singularity_code.modsample.todolist.modTodoListModules 9 | 10 | val allModules = arrayOf( 11 | *coreMainModules, 12 | *coreNetworkModules, 13 | *coreUIModules, 14 | *modTodoListModules, 15 | *modPokemonModules, 16 | *modMMDSampleModules 17 | ) -------------------------------------------------------------------------------- /control/igniter/src/test/java/com/singularity_code/control/igniter/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.igniter 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /control/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 -------------------------------------------------------------------------------- /control/provider/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /control/provider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /** 2 | * Control Module will depend only to core. 3 | * This module act like domain layer for module to module communication. 4 | * Everybody that need to communicate between modules shall depend on this module. 5 | * Since this module also depend to core, this module can act like binder to core API. 6 | * Everybody that implement this module will be able to interact with core utility. 7 | */ 8 | dependencies { 9 | api(project(":core:network")) 10 | api(project(":core:ui")) 11 | } -------------------------------------------------------------------------------- /control/provider/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/control/provider/consumer-rules.pro -------------------------------------------------------------------------------- /control/provider/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 -------------------------------------------------------------------------------- /control/provider/src/androidTest/java/com/singularity_code/control/provider/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.singularity_code.control.provider.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /control/provider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /control/provider/src/main/java/com/singularity_code/control/provider/sample/mmdexample/MMDExampleSpace.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider.sample.mmdexample 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.singularity_code.core.common.pattern.JsonConvertible 5 | import com.singularity_code.core.common.model.NOTHING 6 | import com.singularity_code.core.common.pattern.Payload 7 | import com.singularity_code.core.common.pattern.navigation.Space 8 | 9 | interface MMDExampleSpace : Space { 10 | data class Pld( 11 | @field:SerializedName("nothing") 12 | val nothing: NOTHING = NOTHING() 13 | ) : Payload, JsonConvertible 14 | 15 | data class Result( 16 | val nothing: NOTHING = NOTHING() 17 | ) 18 | } -------------------------------------------------------------------------------- /control/provider/src/main/java/com/singularity_code/control/provider/sample/pokemon/PokemonSpace.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider.sample.pokemon 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.singularity_code.core.common.pattern.JsonConvertible 5 | import com.singularity_code.core.common.model.NOTHING 6 | import com.singularity_code.core.common.pattern.Payload 7 | import com.singularity_code.core.common.pattern.navigation.Space 8 | 9 | interface PokemonSpace : Space { 10 | data class Pld( 11 | @field:SerializedName("nothing") 12 | val nothing: NOTHING = NOTHING() 13 | ) : Payload, JsonConvertible 14 | 15 | data class Result( 16 | val nothing: NOTHING = NOTHING() 17 | ) 18 | } -------------------------------------------------------------------------------- /control/provider/src/main/java/com/singularity_code/control/provider/sample/pokemon/PokemonSpaceStation.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider.sample.pokemon 2 | 3 | import arrow.core.Either 4 | import com.singularity_code.control.provider.sample.pokemon.model.PokemonGem 5 | import com.singularity_code.control.provider.sample.pokemon.payload.GetPokemonByIdSPLD 6 | import com.singularity_code.core.common.model.VmError 7 | import com.singularity_code.core.common.pattern.navigation.SpaceStation 8 | 9 | interface PokemonSpaceStation : SpaceStation { 10 | suspend fun getPokemonById( 11 | payload: GetPokemonByIdSPLD 12 | ): Either 13 | } -------------------------------------------------------------------------------- /control/provider/src/main/java/com/singularity_code/control/provider/sample/pokemon/model/PokemonGem.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider.sample.pokemon.model 2 | 3 | data class PokemonGem( 4 | val id: String, 5 | val name: String 6 | ) -------------------------------------------------------------------------------- /control/provider/src/main/java/com/singularity_code/control/provider/sample/pokemon/payload/GetPokemonByIdSPLD.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider.sample.pokemon.payload 2 | 3 | import com.singularity_code.core.common.pattern.Payload 4 | 5 | data class GetPokemonByIdSPLD( 6 | val id: Int 7 | ) : Payload -------------------------------------------------------------------------------- /control/provider/src/main/java/com/singularity_code/control/provider/sample/todolist/TodoSpace.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider.sample.todolist 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.singularity_code.core.common.pattern.JsonConvertible 5 | import com.singularity_code.core.common.model.NOTHING 6 | import com.singularity_code.core.common.pattern.Payload 7 | import com.singularity_code.core.common.pattern.navigation.Space 8 | 9 | interface TodoSpace : Space { 10 | data class Pld( 11 | @field:SerializedName("nothing") 12 | val nothing: NOTHING = NOTHING() 13 | ) : JsonConvertible, Payload 14 | 15 | data class Result( 16 | val nothing: NOTHING = NOTHING() 17 | ) 18 | } -------------------------------------------------------------------------------- /control/provider/src/test/java/com/singularity_code/control/provider/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.control.provider 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /control/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.BaseExtension 2 | import com.singularity_code.plugins.defaultLibraryConfig 3 | import com.singularity_code.plugins.getKeyStoreProperties 4 | 5 | plugins { 6 | id("java-platform") 7 | } 8 | 9 | group = "android" 10 | version = "1.0.0" 11 | 12 | // Load keystore 13 | val keystoreProperties = getKeyStoreProperties( 14 | "${rootDir.absolutePath}/keystore.properties" 15 | ) 16 | 17 | subprojects { 18 | apply(plugin = "com.android.library") 19 | apply(plugin = "org.jetbrains.kotlin.android") 20 | apply(plugin = "kotlin-android") 21 | apply(plugin = "kotlin-parcelize") 22 | apply(plugin = "kotlin-kapt") 23 | 24 | plugins.withType(BasePlugin::class.java).configureEach { 25 | configure { 26 | defaultLibraryConfig( 27 | versionCode = 1, 28 | versionName = "1.0", 29 | keystoreProperties = keystoreProperties 30 | ) 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | constraints { 37 | api(project(":core:network")) 38 | api(project(":core:common")) 39 | api(project(":core:ui")) 40 | } 41 | } -------------------------------------------------------------------------------- /core/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | android { 2 | kotlinOptions { 3 | jvmTarget = "1.8" 4 | } 5 | buildFeatures { 6 | compose = true 7 | } 8 | composeOptions { 9 | kotlinCompilerExtensionVersion = "1.1.1" 10 | } 11 | } 12 | 13 | dependencies { 14 | 15 | // CORE 16 | api(libs.bundles.core) 17 | testApi(libs.bundles.core.test) 18 | androidTestApi(libs.bundles.core.android.test) 19 | 20 | // ARROW 21 | api(libs.bundles.kotlin.arrow) 22 | 23 | // UI 24 | api(libs.bundles.compose) 25 | testApi(libs.bundles.compose.test) 26 | androidTestApi(libs.bundles.compose.android.test) 27 | debugApi(libs.bundles.compose.debug) 28 | 29 | // Accompanist 30 | api(libs.bundles.accompanist) 31 | 32 | // INJECTION 33 | api(libs.bundles.koin) 34 | 35 | // RETROFIT 36 | api(libs.bundles.retrofit) 37 | 38 | // APOLLO 39 | api(libs.bundles.apollo) 40 | 41 | // BUILDER 42 | kapt(libs.bundles.builder.kapt) 43 | 44 | // PREFERENCE 45 | api(libs.bundles.preference) 46 | 47 | // GSON 48 | api(libs.bundles.serialization) 49 | 50 | // CHUCKER 51 | api(libs.bundles.chucker.debug) 52 | 53 | // IMAGE LOADER 54 | api(libs.bundles.image.loader) 55 | 56 | } -------------------------------------------------------------------------------- /core/common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/common/consumer-rules.pro -------------------------------------------------------------------------------- /core/common/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.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/common/src/androidTest/java/com/singularity_code/core/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.singularity_code.core.common.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /core/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/DI.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.common 6 | 7 | import org.koin.android.ext.koin.androidApplication 8 | import org.koin.android.ext.koin.androidContext 9 | import org.koin.dsl.module 10 | 11 | val coreMainModules = arrayOf( 12 | module { 13 | factory { androidApplication() } 14 | factory { androidContext() } 15 | } 16 | ) -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/model/Data.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class Data( 6 | @field:SerializedName("data") 7 | val data: T? 8 | ) -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/model/NOTHING.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.singularity_code.core.common.pattern.JsonConvertible 5 | 6 | data class NOTHING( 7 | @field:SerializedName("value") 8 | private val value: String? = null 9 | ) : JsonConvertible -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/model/Toast.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common.model 2 | 3 | import com.singularity_code.core.common.pattern.Payload 4 | 5 | data class Toast( 6 | val message: String, 7 | val longToast: Boolean = false 8 | ) : Payload -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/model/VmError.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.common.model 6 | 7 | import com.google.gson.annotations.SerializedName 8 | import com.singularity_code.core.common.pattern.JsonConvertible 9 | 10 | data class VmError( 11 | @field:SerializedName("message") 12 | val message: String?, 13 | @field:SerializedName("code") 14 | val code: Int? 15 | ) : JsonConvertible -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/Bootstrap.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern 5 | 6 | interface Bootstrap { 7 | fun initData() 8 | fun initUI() 9 | fun initAction() 10 | fun initObserver() 11 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/ClearAble.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern 5 | 6 | interface ClearAble { 7 | fun clear() 8 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/Component.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern 5 | 6 | interface Component { 7 | val TAG: String 8 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/JsonConvertible.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern 5 | 6 | import com.google.gson.Gson 7 | 8 | interface JsonConvertible { 9 | fun toStringJSON(): String = Gson().toJson(this) 10 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/Payload.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common.pattern 2 | 3 | interface Payload { 4 | fun getQueryMap(): Map = hashMapOf() 5 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/Readme.md: -------------------------------------------------------------------------------- 1 | Do DRY, all pattern goes here. -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/State.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern 5 | 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.State 8 | import arrow.core.Either 9 | import com.singularity_code.core.common.model.VmError 10 | import com.singularity_code.core.common.util.viewmodel.RequestState 11 | 12 | interface State { 13 | @Composable 14 | fun collectAsState(): State> 15 | @Composable 16 | fun value(): RequestState 17 | fun requestUpdate(payload: P) 18 | fun request(payload: P) 19 | fun resetClear() 20 | val operator: suspend (payload: P) -> Either 21 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/activity/ActivityResultEmitter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.activity 5 | 6 | import android.app.Activity 7 | import androidx.activity.ComponentActivity 8 | import com.singularity_code.core.common.pattern.JsonConvertible 9 | 10 | interface ActivityResultEmitter { 11 | enum class KEY { 12 | PAYLOAD, 13 | RESULT 14 | } 15 | 16 | fun Activity.activityReturnOK(data: O) { 17 | intent.putExtra(KEY.RESULT.name, data.toStringJSON()) 18 | setResult(ComponentActivity.RESULT_OK, intent) 19 | finish() 20 | } 21 | 22 | fun Activity.activityReturnCancel() { 23 | setResult(ComponentActivity.RESULT_CANCELED) 24 | finish() 25 | } 26 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/activity/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.activity 5 | 6 | import com.singularity_code.core.common.pattern.Bootstrap 7 | import com.singularity_code.core.common.pattern.Component 8 | import kotlinx.coroutines.Job 9 | 10 | interface BaseActivity : Component, Bootstrap { 11 | fun withScope(block: suspend () -> Unit): Job 12 | 13 | /** 14 | * On early create - This scope run in onCreate() before everything else. 15 | * This function guarantee the lifecycle is already created when it launched. 16 | * 17 | */ 18 | fun onEarlyCreate() 19 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/activity/BaseActivityAbs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.activity 5 | 6 | import android.os.Bundle 7 | import androidx.activity.ComponentActivity 8 | import androidx.activity.compose.setContent 9 | import androidx.compose.runtime.Composable 10 | import androidx.lifecycle.lifecycleScope 11 | import kotlinx.coroutines.Job 12 | import kotlinx.coroutines.launch 13 | 14 | abstract class BaseActivityAbs : ComponentActivity(), BaseActivity { 15 | 16 | override val TAG = this.javaClass.simpleName 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | onEarlyCreate() 21 | 22 | setContent { 23 | content() 24 | } 25 | 26 | initData() 27 | initUI() 28 | initObserver() 29 | initAction() 30 | } 31 | 32 | override fun onEarlyCreate() {} 33 | 34 | override fun initData() {} 35 | 36 | override fun initUI() {} 37 | 38 | override fun initObserver() {} 39 | 40 | override fun initAction() {} 41 | 42 | override fun withScope(block: suspend () -> Unit): Job { 43 | return lifecycleScope.launch { 44 | block.invoke() 45 | } 46 | } 47 | 48 | abstract val content: @Composable () -> Unit 49 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/activity/LauncherAbs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.activity 5 | 6 | import android.app.Activity 7 | import android.content.Context 8 | import android.content.Intent 9 | import androidx.activity.result.contract.ActivityResultContract 10 | import com.singularity_code.core.common.pattern.JsonConvertible 11 | import com.singularity_code.core.common.util.toObject 12 | 13 | abstract class LauncherAbs

14 | : ActivityResultContract() { 15 | 16 | abstract val intent: (c: Context) -> Intent 17 | abstract val outputType: Class 18 | 19 | override fun createIntent(context: Context, input: P): Intent { 20 | return with(intent.invoke(context)) { 21 | putExtra( 22 | ActivityResultEmitter.KEY.PAYLOAD.name, 23 | input.toStringJSON() 24 | ) 25 | this 26 | } 27 | } 28 | 29 | override fun parseResult(resultCode: Int, intent: Intent?): O? { 30 | return if (resultCode == Activity.RESULT_OK) 31 | intent 32 | ?.getStringExtra( 33 | ActivityResultEmitter.KEY.RESULT.name 34 | ) 35 | ?.toObject( 36 | outputType 37 | ) 38 | ?: run { 39 | // TODO: firebase crash analytic here 40 | throw NullPointerException() 41 | } 42 | else null 43 | } 44 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/application/BaseApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.application 5 | 6 | import android.annotation.SuppressLint 7 | import android.app.Application 8 | import android.content.Context 9 | import com.singularity_code.core.common.util.application.ApplicationContext 10 | import org.koin.android.ext.koin.androidContext 11 | import org.koin.android.ext.koin.androidLogger 12 | import org.koin.core.context.startKoin 13 | import org.koin.core.logger.Level 14 | import org.koin.core.module.Module 15 | 16 | abstract class BaseApplication : Application() { 17 | abstract val modules: Array 18 | 19 | companion object { 20 | @SuppressLint("StaticFieldLeak") 21 | var context: Context? = null 22 | } 23 | 24 | override fun onCreate() { 25 | super.onCreate() 26 | startKoin { 27 | androidContext(applicationContext) 28 | modules(modules.toList()) 29 | androidLogger(Level.INFO) 30 | } 31 | ApplicationContext.set(applicationContext) 32 | } 33 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/navigation/BaseNavigation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.navigation 5 | 6 | interface BaseNavigation { 7 | fun goBack() 8 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/navigation/Portal.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha + Joseph Sanjaya. 3 | */ 4 | package com.singularity_code.core.common.pattern.navigation 5 | 6 | interface Portal -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/navigation/Space.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha + Joseph Sanjaya. 3 | */ 4 | package com.singularity_code.core.common.pattern.navigation 5 | 6 | import androidx.activity.result.contract.ActivityResultContract 7 | 8 | interface Space { 9 | fun getLauncher(): ActivityResultContract 10 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/navigation/SpaceStation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.navigation 5 | 6 | interface SpaceStation -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/viewmodel/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.viewmodel 5 | 6 | import arrow.core.Either 7 | import com.singularity_code.core.common.pattern.ClearAble 8 | import com.singularity_code.core.common.pattern.Payload 9 | import com.singularity_code.core.common.model.VmError 10 | 11 | interface BaseViewModel : ClearAble { 12 | fun

createState( 13 | block: suspend (payload: P) -> Either 14 | ): BaseViewModelAbs.State 15 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/pattern/viewmodel/BaseViewModelAbs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.pattern.viewmodel 5 | 6 | import android.util.Log 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.collectAsState 9 | import androidx.lifecycle.ViewModel 10 | import androidx.lifecycle.viewModelScope 11 | import arrow.core.Either 12 | import com.singularity_code.core.common.pattern.Payload 13 | import com.singularity_code.core.common.util.viewmodel.Default 14 | import com.singularity_code.core.common.util.viewmodel.Failed 15 | import com.singularity_code.core.common.util.viewmodel.Loading 16 | import com.singularity_code.core.common.util.viewmodel.Success 17 | import com.singularity_code.core.common.model.VmError 18 | import com.singularity_code.core.common.util.viewmodel.RequestState 19 | import kotlinx.coroutines.* 20 | import kotlinx.coroutines.flow.MutableStateFlow 21 | import kotlin.coroutines.CoroutineContext 22 | 23 | abstract class BaseViewModelAbs : ViewModel(), BaseViewModel { 24 | 25 | abstract inner class State : 26 | com.singularity_code.core.common.pattern.State { 27 | private val state = MutableStateFlow>(Default()) 28 | private var job: Job? = null 29 | 30 | private val streamSuperVisor = SupervisorJob() 31 | private val handler: (onError: ((c: CoroutineContext, e: Throwable) -> Unit)?) -> CoroutineExceptionHandler = 32 | { 33 | CoroutineExceptionHandler { coroutineContext, throwable -> 34 | it?.invoke(coroutineContext, throwable) 35 | ?: run { 36 | Log.e( 37 | "${coroutineContext::class.simpleName}", 38 | "launchJob: ${throwable.localizedMessage}" 39 | ) 40 | } 41 | } 42 | } 43 | 44 | private val streamScope = CoroutineScope( 45 | streamSuperVisor 46 | + Dispatchers.IO 47 | + handler { c, e -> 48 | state.value = Failed( 49 | VmError( 50 | message = e.message.toString(), 51 | code = 0 52 | ) 53 | ) 54 | }) 55 | 56 | @Composable 57 | override fun collectAsState() = state.collectAsState() 58 | 59 | /** 60 | * warning: this function trigger recomposition 61 | */ 62 | @Composable 63 | override fun value() = collectAsState().value 64 | 65 | override fun requestUpdate(payload: P) { 66 | state.value = Loading() 67 | job?.cancel() 68 | job = viewModelScope.launch( 69 | streamSuperVisor + 70 | Dispatchers.IO + 71 | handler { c, e -> 72 | state.value = Failed( 73 | VmError( 74 | message = e.message.toString(), 75 | code = 0 76 | ) 77 | ) 78 | } 79 | ) { 80 | state.value = operator(payload).fold( 81 | ifLeft = { Failed(it) }, 82 | ifRight = { Success(it) } 83 | ) 84 | } 85 | } 86 | 87 | @Deprecated("Semantic correction. Soon be deleted.", ReplaceWith("requestUpdate(payload)")) 88 | override fun request(payload: P) { 89 | requestUpdate(payload) 90 | } 91 | 92 | override fun resetClear() { 93 | job?.cancel() 94 | state.value = Default() 95 | } 96 | 97 | /** 98 | * Operator will handle state update request 99 | * @param payload : Payload. 100 | */ 101 | abstract override val operator: suspend (payload: P) -> Either 102 | } 103 | 104 | override fun

createState( 105 | block: suspend (payload: P) -> Either 106 | ): State { 107 | return object : State() { 108 | override val operator: suspend (payload: P) -> Either 109 | get() = { block.invoke(it) } 110 | } 111 | } 112 | 113 | abstract override fun clear() 114 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Aliases.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | typealias StringJson = String -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Apollo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.common.util 6 | 7 | import arrow.core.Either 8 | import com.apollographql.apollo3.ApolloCall 9 | import com.apollographql.apollo3.api.Operation 10 | import com.singularity_code.core.common.model.VmError 11 | 12 | 13 | suspend fun ApolloCall.getOrError() 14 | : Either { 15 | 16 | val response = this.execute() 17 | 18 | return when (val data = response.data) { 19 | null -> Either.Left( 20 | VmError( 21 | code = null, 22 | message = response.errors 23 | ?.toString() ?: "Data null" 24 | ) 25 | ) 26 | else -> Either.Right( 27 | data 28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.common.util 6 | 7 | import arrow.core.Either 8 | import com.singularity_code.core.common.pattern.Payload 9 | import com.singularity_code.core.common.pattern.viewmodel.BaseViewModelAbs 10 | import com.singularity_code.core.common.model.VmError 11 | 12 | @Deprecated( 13 | "Semantic correction. Soon be deleted", 14 | ReplaceWith("createStateHolder(operator)") 15 | ) 16 | fun BaseViewModelAbs.stateCreator( 17 | operator: suspend (P) -> Either 18 | ): BaseViewModelAbs.State { 19 | return createStateHolder(operator) 20 | } 21 | 22 | fun BaseViewModelAbs.createStateHolder( 23 | updateOperator: suspend (P) -> Either 24 | ): BaseViewModelAbs.State = object : BaseViewModelAbs.State() { 25 | override val operator = updateOperator 26 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Coroutine.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.launch 9 | 10 | fun LaunchInIO(block: suspend CoroutineScope.() -> Unit) { 11 | CoroutineScope(Dispatchers.IO).launch { block.invoke(this) } 12 | } 13 | 14 | fun LaunchInMain(block: suspend CoroutineScope.() -> Unit) { 15 | CoroutineScope(Dispatchers.Main).launch { block.invoke(this) } 16 | } 17 | 18 | fun LaunchInDefault(block: suspend CoroutineScope.() -> Unit) { 19 | CoroutineScope(Dispatchers.Default).launch { block.invoke(this) } 20 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Date.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import java.text.SimpleDateFormat 7 | import java.time.Instant 8 | import java.time.LocalDateTime 9 | import java.time.ZoneId 10 | import java.util.* 11 | 12 | fun String.toDateFormat(): String? { 13 | val formatted = SimpleDateFormat("dd/MM/yyyy") 14 | return this.isoFormatToDate()?.let { 15 | formatted.format(it) 16 | } 17 | } 18 | 19 | fun String.toTimeFormat(): String? { 20 | val formatted = SimpleDateFormat("HH.mm") 21 | return this.isoFormatToDate()?.let { 22 | formatted.format(it) 23 | } 24 | } 25 | 26 | fun String.isoFormatToDate(): Date? { 27 | val converted = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") 28 | return if (this.isNotEmpty()) converted.parse(this) else Date() 29 | } 30 | 31 | fun Long.toInstant(): Instant { 32 | return Instant.ofEpochMilli(this) 33 | } 34 | 35 | fun Long.toLocalDateTime(): LocalDateTime { 36 | return LocalDateTime.ofInstant(toInstant(), ZoneId.systemDefault()) 37 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Json.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common.util 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.GsonBuilder 5 | import com.singularity_code.core.common.pattern.JsonConvertible 6 | 7 | 8 | inline fun String.toObject(): T = Gson().fromJson(this, T::class.java) 9 | 10 | fun String.toObject(type: Class): T { 11 | val gson = GsonBuilder().create() 12 | return gson.fromJson(this, type) 13 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Launcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import android.content.Context 7 | import android.content.Intent 8 | import androidx.activity.result.ActivityResultCallback 9 | import androidx.activity.result.ActivityResultLauncher 10 | import com.singularity_code.core.common.pattern.Payload 11 | import com.singularity_code.core.common.pattern.activity.LauncherAbs 12 | import com.singularity_code.core.common.pattern.JsonConvertible 13 | import com.singularity_code.core.common.pattern.activity.BaseActivityAbs 14 | import com.singularity_code.core.common.pattern.navigation.Space 15 | 16 | fun

createLauncher( 17 | activity: Class<*>, 18 | outputType: Class 19 | ): LauncherAbs { 20 | return object : LauncherAbs() { 21 | override val intent: (c: Context) -> Intent 22 | get() = { c -> Intent(c, activity) } 23 | override val outputType: Class 24 | get() = outputType 25 | } 26 | } 27 | 28 | fun BaseActivityAbs.createLauncher( 29 | space: Space, 30 | callback: ActivityResultCallback 31 | ): ActivityResultLauncher

{ 32 | return registerForActivityResult(space.getLauncher(), callback) 33 | } 34 | 35 | /** 36 | * @param S for Space 37 | * @param P for Payload 38 | * @param R for Result 39 | */ 40 | class Launcher, P : Payload, R>( 41 | private val space: S, 42 | private val activity: BaseActivityAbs 43 | ) { 44 | /** 45 | * Override this value for callback action 46 | */ 47 | var resultCallback: ((R) -> Unit)? = null 48 | 49 | private val launcher = activity.createLauncher(space) { 50 | if (it != null) 51 | resultCallback?.invoke(it) 52 | } 53 | 54 | fun launch( 55 | payload: P 56 | ) { 57 | launcher.launch(payload) 58 | } 59 | } 60 | 61 | /** 62 | * @param S for Space 63 | * @param P for Payload 64 | * @param R for Result 65 | * @param space for Space 66 | */ 67 | fun , P : Payload, R> BaseActivityAbs.launcher( 68 | space: S 69 | ) = Launcher( 70 | space = space, 71 | activity = this 72 | ) -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/List.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | fun List?.refineList() = 7 | this?.filterNotNull() ?: listOf() -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Log.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import android.util.Log 7 | 8 | fun Any.logDebug(msg: String) { 9 | Log.d(this.javaClass.simpleName, msg) 10 | } 11 | 12 | fun logDebug(msg: String) { 13 | Log.d("NOWHERE", msg) 14 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Long.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | fun Long.toTimeFormatted( 7 | showHour: Boolean = true, 8 | showMinute: Boolean = true, 9 | showSecond: Boolean = true 10 | ): String { 11 | var s = this / 1000 12 | val h = s / 3600 13 | s -= (h * 3600) 14 | val m = s / 60 15 | s -= (m * 60) 16 | 17 | return with(StringBuilder()) { 18 | 19 | if (showHour) { 20 | if (h < 10) { 21 | append("0") 22 | } 23 | append(h) 24 | } 25 | 26 | if (showMinute) { 27 | if (showHour) { 28 | append(":") 29 | } 30 | 31 | if (m < 10) { 32 | append("0") 33 | } 34 | append(m) 35 | } 36 | 37 | if (showSecond) { 38 | if (showHour || showMinute) { 39 | append(":") 40 | } 41 | 42 | if (s < 10) { 43 | append("0") 44 | } 45 | append(s) 46 | } 47 | 48 | toString() 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Null.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | fun withNotNull(entity: T?, block: (T) -> Unit) { 7 | entity?.apply { 8 | block.invoke(this) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Readme.md: -------------------------------------------------------------------------------- 1 | Util should only contain functions. -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/State.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.common.util 6 | 7 | import androidx.compose.runtime.Composable 8 | import com.singularity_code.core.common.model.VmError 9 | import com.singularity_code.core.common.pattern.State 10 | import com.singularity_code.core.common.util.viewmodel.Default 11 | import com.singularity_code.core.common.util.viewmodel.Failed 12 | import com.singularity_code.core.common.util.viewmodel.Loading 13 | import com.singularity_code.core.common.util.viewmodel.Success 14 | 15 | /** 16 | * Be careful this function trigger recomposition. 17 | * Use this if you only need to handle a state one time only. 18 | * This function wil map state value to given response type. 19 | */ 20 | 21 | @Deprecated("Warning: this method arguments is not yet immutable arguments, so becareful when using it.") 22 | @Composable 23 | fun State.mapState( 24 | default: () -> R, 25 | loading: () -> R, 26 | failed: (e: VmError) -> R, 27 | success: (d: T) -> R 28 | ): R { 29 | return when (val v = value()) { 30 | is Default -> default.invoke() 31 | is Failed -> failed.invoke(v.e) 32 | is Loading -> loading.invoke() 33 | is Success -> success.invoke(v.value) 34 | } 35 | } 36 | 37 | /** 38 | * Be careful this function trigger recomposition. 39 | * Use this if you only need to handle a state one time only. 40 | * This function will handle each state by emitting a composable function. 41 | */ 42 | @Composable 43 | fun State.onState( 44 | default: @Composable () -> Unit, 45 | loading: @Composable () -> Unit, 46 | failed: @Composable (e: VmError) -> Unit, 47 | success: @Composable (d: T) -> Unit, 48 | ) { 49 | return when (val v = value()) { 50 | is Default -> default() 51 | is Failed -> failed(v.e) 52 | is Loading -> loading() 53 | is Success -> success(v.value) 54 | } 55 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/Toast.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import android.widget.Toast.makeText 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.platform.LocalContext 9 | import com.singularity_code.core.common.model.Toast 10 | 11 | @Composable 12 | fun MakeToast(payload: Toast) = makeText( 13 | LocalContext.current.applicationContext, 14 | payload.message, 15 | if (payload.longToast) 16 | android.widget.Toast.LENGTH_LONG 17 | else android.widget.Toast.LENGTH_SHORT 18 | ).show() -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/application/ApplicationContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util.application 5 | 6 | import android.annotation.SuppressLint 7 | import android.content.Context 8 | 9 | @SuppressLint("StaticFieldLeak") 10 | object ApplicationContext { 11 | private var context: Context? = null 12 | fun set(context: Context) { 13 | ApplicationContext.context = context 14 | } 15 | 16 | fun get(): Context { 17 | return context!! 18 | } 19 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/injection/Koin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import org.koin.core.component.KoinComponent 7 | import org.koin.core.component.inject 8 | 9 | inline fun get(): T { 10 | val koinObj = object : KoinComponent { 11 | val instance: T by inject() 12 | } 13 | return koinObj.instance 14 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/network/RetrofitUtil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util 5 | 6 | import androidx.annotation.NonNull 7 | import arrow.retrofit.adapter.either.EitherCallAdapterFactory 8 | import okhttp3.OkHttpClient 9 | import retrofit2.Retrofit 10 | import retrofit2.converter.gson.GsonConverterFactory 11 | 12 | /** 13 | * Create retrofit service 14 | * @author stefanus.ayudha@gmail.com 15 | * @param I Retrofit interface 16 | * @param service Class of the given Retrofit interface 17 | * @param httpClient OkHttp client, if you are using my codebase all u need is injecting it by parsing get() as it's param in koin module injection configuration, once you inject the core modules configuration to the Application 18 | * @param baseUrl Base Url of the web api 19 | * @return Retrofit service of the given interface 20 | */ 21 | fun createRetrofitService( 22 | @NonNull service: Class, 23 | httpClient: OkHttpClient, 24 | baseUrl: String, 25 | ): I = Retrofit.Builder() 26 | .client(httpClient) 27 | .baseUrl(baseUrl) 28 | .addCallAdapterFactory(EitherCallAdapterFactory()) 29 | .addConverterFactory(GsonConverterFactory.create()) 30 | .build() 31 | .create(service) -------------------------------------------------------------------------------- /core/common/src/main/java/com/singularity_code/core/common/util/viewmodel/RequestState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | package com.singularity_code.core.common.util.viewmodel 5 | 6 | import com.singularity_code.core.common.model.VmError 7 | 8 | sealed class RequestState 9 | class Default : RequestState() 10 | class Loading : RequestState() 11 | class Success(var value: T) : RequestState() 12 | class Failed(val e: VmError) : RequestState() -------------------------------------------------------------------------------- /core/common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Singularity Software 3 | -------------------------------------------------------------------------------- /core/common/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /core/common/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /core/common/src/test/java/com/singularity_code/core/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.common 2 | 3 | import org.junit.Assert.assertEquals 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/consumer-rules.pro -------------------------------------------------------------------------------- /core/network/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.cxx/ 3 | -------------------------------------------------------------------------------- /core/network/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 Designed and developed by Joseph Sanjaya, S.T., M.Kom., All 2 | # Rights Reserved. 3 | # @Github (https://github.com/JosephSanjaya), 4 | # @LinkedIn (https://www.linkedin.com/in/josephsanjaya/)) 5 | 6 | # For more information about using CMake with Android Studio, read the 7 | # documentation: https://d.android.com/studio/projects/add-native-code.html 8 | 9 | # Sets the minimum version of CMake required to build the native library. 10 | 11 | cmake_minimum_required(VERSION 3.4.1) 12 | 13 | # Creates and names a library, sets it as either STATIC 14 | # or SHARED, and provides the relative paths to its source code. 15 | # You can define multiple libraries, and CMake builds them for you. 16 | # Gradle automatically packages shared libraries with your APK. 17 | 18 | add_library( # Sets the name of the library. 19 | dev-url 20 | 21 | # Sets the library as a shared library. 22 | SHARED 23 | 24 | # Provides a relative path to your source file(s). 25 | src/main/cpp/dev-url.cpp) 26 | 27 | add_library( # Sets the name of the library. 28 | stage-url 29 | 30 | # Sets the library as a shared library. 31 | SHARED 32 | 33 | # Provides a relative path to your source file(s). 34 | src/main/cpp/stage-url.cpp) 35 | 36 | add_library( # Sets the name of the library. 37 | prod-url 38 | 39 | # Sets the library as a shared library. 40 | SHARED 41 | 42 | # Provides a relative path to your source file(s). 43 | src/main/cpp/prod-url.cpp) 44 | 45 | # Searches for a specified prebuilt library and stores the path as a 46 | # variable. Because CMake includes system libraries in the search path by 47 | # default, you only need to specify the name of the public NDK library 48 | # you want to add. CMake verifies that the library exists before 49 | # completing its build. 50 | 51 | find_library( # Sets the name of the path variable. 52 | log-lib 53 | 54 | # Specifies the name of the NDK library that 55 | # you want CMake to locate. 56 | log) 57 | 58 | # Specifies libraries CMake should link to your target library. You 59 | # can link multiple libraries, such as libraries you define in this 60 | # build script, prebuilt third-party libraries, or system libraries. 61 | 62 | target_link_libraries( # Specifies the target library. 63 | dev-url 64 | 65 | # Links the target library to the log library 66 | # included in the NDK. 67 | ${log-lib}) 68 | target_link_libraries( # Specifies the target library. 69 | stage-url 70 | 71 | # Links the target library to the log library 72 | # included in the NDK. 73 | ${log-lib}) 74 | target_link_libraries( # Specifies the target library. 75 | prod-url 76 | 77 | # Links the target library to the log library 78 | # included in the NDK. 79 | ${log-lib}) -------------------------------------------------------------------------------- /core/network/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.apollographql.apollo3").version("3.1.0") 3 | } 4 | 5 | android { 6 | externalNativeBuild { 7 | cmake { 8 | path("CMakeLists.txt") 9 | } 10 | } 11 | } 12 | 13 | dependencies { 14 | api(project(":core:common")) 15 | } 16 | 17 | apollo { 18 | packageName.set("com.singularity_code.core.network") 19 | } -------------------------------------------------------------------------------- /core/network/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/network/consumer-rules.pro -------------------------------------------------------------------------------- /core/network/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.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/network/src/androidTest/java/com/singularity_code/core/network/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.network 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.singularity_code.core.network.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /core/network/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/network/src/main/cpp/dev-url.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefanus Ayudha on 18/04/2022. 3 | // email: stefanus.ayudha@gmail.com 4 | // 5 | 6 | #include 7 | #include 8 | 9 | extern "C" 10 | JNIEXPORT jstring JNICALL 11 | Java_com_singularity_1code_core_network_util_SecuredDev_getBaseUrl(JNIEnv *env, jobject thiz) { 12 | return env->NewStringUTF("https://jsonplaceholder.typicode.com/"); 13 | } 14 | extern "C" 15 | JNIEXPORT jstring JNICALL 16 | Java_com_singularity_1code_core_network_util_SecuredDev_getBasePokemonUrl(JNIEnv *env, jobject thiz) { 17 | return env->NewStringUTF("https://beta.pokeapi.co/graphql/v1beta"); 18 | } -------------------------------------------------------------------------------- /core/network/src/main/cpp/prod-url.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefanus Ayudha on 18/04/2022. 3 | // email: stefanus.ayudha@gmail.com 4 | // 5 | 6 | #include 7 | #include 8 | 9 | extern "C" 10 | JNIEXPORT jstring JNICALL 11 | Java_com_singularity_1code_core_network_util_SecuredProd_getBaseUrl(JNIEnv *env, jobject thiz) { 12 | return env->NewStringUTF("https://jsonplaceholsssssder.typicode.com/"); 13 | } 14 | extern "C" 15 | JNIEXPORT jstring JNICALL 16 | Java_com_singularity_1code_core_network_util_SecuredProd_getBasePokemonUrl(JNIEnv *env, 17 | jobject thiz) { 18 | return env->NewStringUTF("https://beta.pokeapi.co/graphql/v1beta"); 19 | } -------------------------------------------------------------------------------- /core/network/src/main/cpp/stage-url.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Stefanus Ayudha on 18/04/2022. 3 | // email: stefanus.ayudha@gmail.com 4 | // 5 | 6 | #include 7 | #include 8 | 9 | extern "C" 10 | JNIEXPORT jstring JNICALL 11 | Java_com_singularity_1code_core_network_util_SecuredStaging_getBaseUrl(JNIEnv *env, jobject thiz) { 12 | return env->NewStringUTF("https://jsonplaceholder.typicode.com/"); 13 | } 14 | extern "C" 15 | JNIEXPORT jstring JNICALL 16 | Java_com_singularity_1code_core_network_util_SecuredStaging_getBasePokemonUrl(JNIEnv *env, 17 | jobject thiz) { 18 | return env->NewStringUTF("https://beta.pokeapi.co/graphql/v1beta"); 19 | } -------------------------------------------------------------------------------- /core/network/src/main/graphql/GetPokemonById.graphql: -------------------------------------------------------------------------------- 1 | query GetPokemonById( 2 | $id: Int! 3 | ) { 4 | pokemon_v2_pokemon( 5 | where: { 6 | id: { 7 | _eq: $id 8 | } 9 | } 10 | ) { 11 | id 12 | name 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/network/src/main/graphql/GetPokemonList.graphql: -------------------------------------------------------------------------------- 1 | query GetPokemonList( 2 | $limit: Int!, 3 | $offset: Int! 4 | ) { 5 | pokemon_v2_pokemon( 6 | limit: $limit, 7 | offset: $offset 8 | ) { 9 | id 10 | name 11 | } 12 | } -------------------------------------------------------------------------------- /core/network/src/main/java/com/singularity_code/core/network/DI.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.network 6 | 7 | import org.koin.core.module.Module 8 | 9 | val coreNetworkModules = arrayOf() -------------------------------------------------------------------------------- /core/network/src/main/java/com/singularity_code/core/network/data/Secured.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.network.data 6 | 7 | // use to store base URL securely by android NDK 8 | interface SecuredUseCase { 9 | fun getBaseUrl(): String 10 | fun getBasePokemonUrl(): String 11 | } -------------------------------------------------------------------------------- /core/network/src/main/java/com/singularity_code/core/network/util/Apollo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.network.util 6 | 7 | import com.apollographql.apollo3.ApolloClient 8 | import com.apollographql.apollo3.network.okHttpClient 9 | 10 | fun apolloClient( 11 | url: String 12 | ): ApolloClient = 13 | ApolloClient.Builder() 14 | .serverUrl(url) 15 | .okHttpClient( 16 | defaultOkhttp() 17 | ) 18 | .build() -------------------------------------------------------------------------------- /core/network/src/main/java/com/singularity_code/core/network/util/Okhttp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.network.util 6 | 7 | import com.iddevops.core.network.BuildConfig.BUILD_TYPE 8 | import com.singularity_code.core.network.util.interceptor.chuckerInterceptor 9 | import okhttp3.OkHttpClient 10 | 11 | fun defaultOkhttp() = OkHttpClient.Builder() 12 | .apply { 13 | if (BUILD_TYPE != "release") 14 | addInterceptor(chuckerInterceptor) 15 | } 16 | .build() -------------------------------------------------------------------------------- /core/network/src/main/java/com/singularity_code/core/network/util/Secured.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.network.util 2 | 3 | import com.iddevops.core.network.BuildConfig 4 | import com.singularity_code.core.network.data.SecuredUseCase 5 | 6 | 7 | object SecuredDev : SecuredUseCase { 8 | init { 9 | System.loadLibrary("dev-url") 10 | } 11 | 12 | external override fun getBaseUrl(): String 13 | external override fun getBasePokemonUrl(): String 14 | } 15 | 16 | object SecuredStaging: SecuredUseCase { 17 | init { 18 | System.loadLibrary("stage-url") 19 | } 20 | 21 | external override fun getBaseUrl(): String 22 | external override fun getBasePokemonUrl(): String 23 | } 24 | 25 | object SecuredProd: SecuredUseCase { 26 | init { 27 | System.loadLibrary("prod-url") 28 | } 29 | 30 | external override fun getBaseUrl(): String 31 | external override fun getBasePokemonUrl(): String 32 | } 33 | 34 | val Secured: SecuredUseCase by lazy { 35 | when (BuildConfig.BUILD_TYPE) { 36 | "debug" -> SecuredDev 37 | "staging" -> SecuredStaging 38 | else -> SecuredProd 39 | } 40 | } -------------------------------------------------------------------------------- /core/network/src/main/java/com/singularity_code/core/network/util/interceptor/ChuckerInterceptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.network.util.interceptor 6 | 7 | import com.chuckerteam.chucker.api.ChuckerCollector 8 | import com.chuckerteam.chucker.api.ChuckerInterceptor 9 | import com.chuckerteam.chucker.api.RetentionManager 10 | import com.singularity_code.core.common.util.application.ApplicationContext 11 | 12 | val chuckerCollector by lazy { 13 | ChuckerCollector( 14 | context = ApplicationContext.get(), 15 | // Toggles visibility of the notification 16 | showNotification = true, 17 | // Allows to customize the retention period of collected data 18 | retentionPeriod = RetentionManager.Period.ONE_HOUR 19 | ) 20 | } 21 | 22 | val chuckerInterceptor by lazy { 23 | ChuckerInterceptor.Builder(context = ApplicationContext.get()) 24 | // The previously created Collector 25 | .collector(chuckerCollector) 26 | // The max body content length in bytes, after this responses will be truncated. 27 | .maxContentLength(250_000L) 28 | // List of headers to replace with ** in the Chucker UI 29 | // .redactHeaders("Auth-Token", "Bearer") 30 | // Read the whole response body even when the client does not consume the response completely. 31 | // This is useful in case of parsing errors or when the response body 32 | // is closed before being read like in Retrofit with Void and Unit types. 33 | .alwaysReadResponseBody(true) 34 | // Use decoder when processing request and response bodies. When multiple decoders are installed they 35 | // are applied in an order they were added. 36 | // .addBodyDecoder(decoder) 37 | // Controls Android shortcut creation. Available in SNAPSHOTS versions only at the moment 38 | // .createShortcut(true) 39 | .build() 40 | } -------------------------------------------------------------------------------- /core/network/src/test/java/com/singularity_code/core/network/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.network 2 | 3 | import org.junit.Assert.* 4 | import org.junit.Test 5 | 6 | /** 7 | * Example local unit test, which will execute on the development machine (host). 8 | * 9 | * See [testing documentation](http://d.android.com/tools/testing). 10 | */ 11 | class ExampleUnitTest { 12 | @Test 13 | fun addition_isCorrect() { 14 | assertEquals(4, 2 + 2) 15 | } 16 | } -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui/build.gradle.kts: -------------------------------------------------------------------------------- 1 | android { 2 | kotlinOptions { 3 | jvmTarget = "1.8" 4 | } 5 | buildFeatures { 6 | compose = true 7 | } 8 | composeOptions { 9 | kotlinCompilerExtensionVersion = "1.1.1" 10 | } 11 | } 12 | 13 | dependencies { 14 | api(project(":core:common")) 15 | } -------------------------------------------------------------------------------- /core/ui/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/consumer-rules.pro -------------------------------------------------------------------------------- /core/ui/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/ui/src/androidTest/java/com/singularity_code/core/ui/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.* 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.singularity_code.core.ui.test", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /core/ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/DI.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022. Stefanus Ayudha. 3 | */ 4 | 5 | package com.singularity_code.core.ui 6 | 7 | import org.koin.core.module.Module 8 | 9 | val coreUIModules = arrayOf() -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/ContentThemeWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.Surface 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import com.singularity_code.core.ui.util.material3.BaseComposeTheme 9 | 10 | @Composable 11 | fun ContentThemeWrapper( 12 | modifier: Modifier = Modifier, 13 | forceDark: Boolean = isSystemInDarkTheme(), 14 | content: @Composable () -> Unit 15 | ) { 16 | BaseComposeTheme( 17 | darkTheme = forceDark 18 | ) { 19 | // A surface container using the 'background' color from the theme 20 | Surface( 21 | modifier = modifier, 22 | color = MaterialTheme.colorScheme.background, 23 | content = content 24 | ) 25 | } 26 | } -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/Dimen.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util 2 | 3 | import androidx.compose.ui.unit.Dp 4 | import androidx.compose.ui.unit.TextUnit 5 | import androidx.compose.ui.unit.dp 6 | import androidx.compose.ui.unit.sp 7 | 8 | object ScreenSpec { 9 | var scale = 1f 10 | var isTablet = false 11 | } 12 | 13 | /** 14 | * In case you wanted to scale the "dp" and "sp" unit to fit your screen. 15 | * Example case: sometime the design system is build for a specific kind of devices like tablet. 16 | * Most of the time, the component sizes, text elements and others custom elements won't fit in. 17 | * 18 | * Notes: use "toDp" and "toSp" instead of built in "dp" and "sp", 19 | * these extensions designed to scale respect to ScreenSpec configuration. 20 | */ 21 | fun setupScreenSize(scaleScreen: Float = 1f) { 22 | ScreenSpec.scale = scaleScreen 23 | ScreenSpec.isTablet = false 24 | } 25 | 26 | /** 27 | * these extensions design to automatically scale it self, respect to 28 | * ScreenSpec configuration. 29 | * @see com.singularity_code.core.ui.ScreenSpec 30 | */ 31 | val Int.toDp: Dp 32 | get() = if (ScreenSpec.isTablet) this.dp * ScreenSpec.scale else this.dp 33 | 34 | /** 35 | * these extensions design to automatically scale it self, respect to 36 | * ScreenSpec configuration. 37 | * @see com.singularity_code.core.ui.ScreenSpec 38 | */ 39 | val Int.toSp: TextUnit 40 | get() = if (ScreenSpec.isTablet) this.sp * ScreenSpec.scale else this.sp -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/material3/Color.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util.material3 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val SafetyOrange00 = Color(0xFF000000) 6 | val SafetyOrange10 = Color(0xFF331600) 7 | val SafetyOrange20 = Color(0xFF662c00) 8 | val SafetyOrange30 = Color(0xFF994200) 9 | val SafetyOrange40 = Color(0xFFcc5800) 10 | val SafetyOrange50 = Color(0xFFff6e00) 11 | val SafetyOrange60 = Color(0xFFff8b33) 12 | val SafetyOrange70 = Color(0xFFffa866) 13 | val SafetyOrange80 = Color(0xFFffc599) 14 | val SafetyOrange90 = Color(0xFFffe2cc) 15 | val SafetyOrange100 = Color(0xFFFFFFFF) 16 | 17 | val DodgerBlue00 = Color(0xFF000000) 18 | val DodgerBlue10 = Color(0xFF001d33) 19 | val DodgerBlue20 = Color(0xFF003a66) 20 | val DodgerBlue30 = Color(0xFF005799) 21 | val DodgerBlue40 = Color(0xFF0074cc) 22 | val DodgerBlue50 = Color(0xFF0091ff) 23 | val DodgerBlue60 = Color(0xFF33a7ff) 24 | val DodgerBlue70 = Color(0xFF66bdff) 25 | val DodgerBlue80 = Color(0xFF99d3ff) 26 | val DodgerBlue90 = Color(0xFFcce9ff) 27 | val DodgerBlue100 = Color(0xFFFFFFFF) 28 | 29 | val Neutral00 = Color(0xFF000000) 30 | val Neutral10 = Color(0xFF1a1a1a) 31 | val Neutral20 = Color(0xFF333333) 32 | val Neutral25 = Color(0xFF404040) 33 | val Neutral30 = Color(0xFF4d4d4d) 34 | val Neutral40 = Color(0xFF666666) 35 | val Neutral50 = Color(0xFF808080) 36 | val Neutral60 = Color(0xFF999999) 37 | val Neutral70 = Color(0xFFb2b2b2) 38 | val Neutral80 = Color(0xFFcccccc) 39 | val Neutral90 = Color(0xFFe5e5e5) 40 | val Neutral95 = Color(0xFFf2f2f2) 41 | val Neutral97 = Color(0xFFf7f7f7) 42 | val Neutral98 = Color(0xfffafafa) 43 | val Neutral100 = Color(0xFFFFFFFF) 44 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/material3/Dimens.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util.material3 2 | 3 | import com.singularity_code.core.ui.util.toDp 4 | import com.singularity_code.core.ui.util.toSp 5 | 6 | val dp0 = 0.toDp 7 | val dp1 = 1.toDp 8 | val dp2 = 2.toDp 9 | val dp4 = 4.toDp 10 | val dp8 = 8.toDp 11 | val dp10 = 10.toDp 12 | val dp12 = 12.toDp 13 | val dp16 = 16.toDp 14 | val dp20 = 20.toDp 15 | val dp24 = 24.toDp 16 | val dp28 = 28.toDp 17 | val dp32 = 32.toDp 18 | val dp48 = 48.toDp 19 | val dp52 = 52.toDp 20 | val dp56 = 56.toDp 21 | val dp64 = 64.toDp 22 | val dp72 = 72.toDp 23 | val dp88 = 88.toDp 24 | val dp90 = 90.toDp 25 | val dp100 = 100.toDp 26 | val dp120 = 120.toDp 27 | val dp156 = 156.toDp 28 | val dp200 = 200.toDp 29 | val dp260 = 260.toDp 30 | val dp440 = 440.toDp 31 | 32 | val sp10 = 10.toSp 33 | val sp12 = 12.toSp 34 | val sp13 = 13.toSp 35 | val sp14 = 14.toSp 36 | val sp16 = 16.toSp 37 | val sp18 = 18.toSp 38 | val sp20 = 20.toSp 39 | val sp24 = 24.toSp 40 | val sp26 = 26.toSp 41 | val sp32 = 32.toSp 42 | val sp36 = 36.toSp 43 | val sp52 = 52.toSp -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/material3/FontFamily.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util.material3 2 | 3 | import androidx.compose.ui.text.font.Font 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.font.FontWeight 6 | import com.iddevops.core.ui.R 7 | 8 | val InterLight = FontFamily( 9 | Font( 10 | resId = R.font.inter_regular, 11 | weight = FontWeight(300) 12 | ) 13 | ) 14 | 15 | val InterRegular = FontFamily( 16 | Font( 17 | resId = R.font.inter_regular, 18 | weight = FontWeight(400) 19 | ) 20 | ) 21 | 22 | val InterMedium = FontFamily( 23 | Font( 24 | resId = R.font.inter_regular, 25 | weight = FontWeight(500) 26 | ) 27 | ) 28 | 29 | val InterSemiBold = FontFamily( 30 | Font( 31 | resId = R.font.inter_regular, 32 | weight = FontWeight(600) 33 | ) 34 | ) 35 | 36 | val InterBold = FontFamily( 37 | Font( 38 | resId = R.font.inter_regular, 39 | weight = FontWeight(700) 40 | ) 41 | ) 42 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/material3/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util.material3 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | 5 | val rounded4 = RoundedCornerShape(dp4) 6 | val rounded8 = RoundedCornerShape(dp8) 7 | val rounded16 = RoundedCornerShape(dp16) -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/material3/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util.material3 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.os.Build 6 | import androidx.compose.foundation.isSystemInDarkTheme 7 | import androidx.compose.material3.* 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.SideEffect 10 | import androidx.compose.ui.graphics.toArgb 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.compose.ui.platform.LocalView 13 | import androidx.core.view.ViewCompat 14 | 15 | private val LightColorScheme = lightColorScheme( 16 | primary = SafetyOrange50, 17 | onPrimary = Neutral98, 18 | secondary = DodgerBlue50, 19 | onSecondary = Neutral10, 20 | background = Neutral98, 21 | onBackground = Neutral10, 22 | surface = Neutral95, 23 | onSurface = Neutral10, 24 | surfaceVariant = Neutral90, 25 | onSurfaceVariant = Neutral10 26 | ) 27 | 28 | private val DarkColorScheme = darkColorScheme( 29 | primary = SafetyOrange50, 30 | onPrimary = Neutral10, 31 | secondary = DodgerBlue50, 32 | onSecondary = Neutral10, 33 | background = Neutral10, 34 | onBackground = Neutral90, 35 | surface = Neutral20, 36 | onSurface = Neutral90, 37 | surfaceVariant = Neutral25, 38 | onSurfaceVariant = Neutral90 39 | ) 40 | 41 | @Composable 42 | fun BaseComposeTheme( 43 | darkTheme: Boolean = isSystemInDarkTheme(), 44 | // Dynamic color is available on Android 12+ 45 | dynamicColor: Boolean = true, 46 | content: @Composable () -> Unit 47 | ) { 48 | val colorScheme = when { 49 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 50 | val context = LocalContext.current 51 | if (darkTheme) 52 | // DarkColorScheme 53 | dynamicDarkColorScheme(context) 54 | else 55 | // LightColorScheme 56 | dynamicLightColorScheme(context) 57 | } 58 | darkTheme -> DarkColorScheme 59 | else -> LightColorScheme 60 | } 61 | val view = LocalView.current 62 | if (!view.isInEditMode) { 63 | SideEffect { 64 | (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb() 65 | ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme 66 | } 67 | } 68 | 69 | MaterialTheme( 70 | colorScheme = colorScheme, 71 | typography = Typography, 72 | content = content 73 | ) 74 | } -------------------------------------------------------------------------------- /core/ui/src/main/java/com/singularity_code/core/ui/util/material3/Type.kt: -------------------------------------------------------------------------------- 1 | package com.singularity_code.core.ui.util.material3 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.text.style.TextDecoration 8 | import androidx.compose.ui.unit.sp 9 | 10 | // Set of Material typography styles to start with 11 | val Typography = Typography( 12 | bodyLarge = TextStyle( 13 | fontFamily = FontFamily.Default, 14 | fontWeight = FontWeight.Normal, 15 | fontSize = 16.sp, 16 | lineHeight = 24.sp, 17 | letterSpacing = 0.5.sp 18 | ) 19 | ) -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_singularity_logo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 48 | -------------------------------------------------------------------------------- /core/ui/src/main/res/drawable/ic_singularity_logo_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 47 | -------------------------------------------------------------------------------- /core/ui/src/main/res/font/inter_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/font/inter_regular.ttf -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanusayudha/base-android-compose/663d4d4ef63f43b2d14233a71c04605539998403/core/ui/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /core/ui/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |