├── common
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── common
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mutualmobile
│ │ └── praxis
│ │ └── common
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── data
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── data
│ │ │ ├── AppConstants.kt
│ │ │ ├── mapper
│ │ │ └── EntityMapper.kt
│ │ │ ├── sources
│ │ │ ├── IJokesRemoteSource.kt
│ │ │ └── JokesRemoteSource.kt
│ │ │ ├── remote
│ │ │ ├── JokeApiService.kt
│ │ │ ├── SafeApiCall.kt
│ │ │ ├── model
│ │ │ │ └── NETJokeListData.kt
│ │ │ └── RetrofitHelper.kt
│ │ │ ├── injection
│ │ │ ├── SourcesModule.kt
│ │ │ ├── RepositoryModule.kt
│ │ │ └── NetworkModule.kt
│ │ │ └── repository
│ │ │ └── JokesRepoImpl.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── data
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mutualmobile
│ │ └── praxis
│ │ └── data
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── domain
├── .gitignore
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── domain
│ │ │ ├── repository
│ │ │ └── IJokesRepo.kt
│ │ │ ├── mappers
│ │ │ └── DomainModel.kt
│ │ │ ├── model
│ │ │ └── DOMJoke.kt
│ │ │ ├── injection
│ │ │ └── UseCaseModule.kt
│ │ │ ├── usecases
│ │ │ ├── BaseUseCase.kt
│ │ │ └── GetFiveRandomJokesUseCase.kt
│ │ │ └── SafeResult.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── domain
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mutualmobile
│ │ └── praxis
│ │ └── domain
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── app
├── .gitignore
├── keystore
│ ├── praxis-debug.jks
│ └── praxis-release.jks
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ └── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── mutualmobile
│ │ │ │ └── praxis
│ │ │ │ ├── PraxisApp.kt
│ │ │ │ ├── injection
│ │ │ │ └── module
│ │ │ │ │ └── NavigationModule.kt
│ │ │ │ └── root
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── PraxisNavigation.kt
│ │ └── AndroidManifest.xml
│ └── test
│ │ ├── java
│ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ ├── utils
│ │ │ ├── FileUtil.kt
│ │ │ └── TestKotlinExtensions.kt
│ │ │ ├── TestApplication.kt
│ │ │ ├── useCaseTest
│ │ │ └── GetFiveRandomJokesUseCaseTest.kt
│ │ │ ├── base
│ │ │ └── BaseTest.kt
│ │ │ └── injection
│ │ │ └── module
│ │ │ └── FakeNetworkModule.kt
│ │ └── resources
│ │ └── responses
│ │ └── jokes_response.json
├── fabric.properties
├── signing.gradle.kts
├── proguard-rules.pro
├── variants.gradle.kts
├── proguard-specific.txt
├── build.gradle.kts
└── proguard-common.txt
├── commonui
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── integer.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── anim
│ │ │ │ ├── slide_left_in.xml
│ │ │ │ ├── slide_left_out.xml
│ │ │ │ ├── slide_right_in.xml
│ │ │ │ └── slide_right_out.xml
│ │ │ ├── values-v21
│ │ │ │ └── styles.xml
│ │ │ └── drawable
│ │ │ │ ├── ic_email.xml
│ │ │ │ └── ic_eye.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── commonui
│ │ │ ├── theme
│ │ │ ├── Shape.kt
│ │ │ ├── Type.kt
│ │ │ ├── Color.kt
│ │ │ ├── PraxisSurface.kt
│ │ │ ├── SystemUiController.kt
│ │ │ └── Theme.kt
│ │ │ └── material
│ │ │ ├── CommonTopAppBar.kt
│ │ │ └── DefaultSnackbar.kt
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── commonui
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mutualmobile
│ │ └── praxis
│ │ └── commonui
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── navigator
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── mutualmobile
│ │ │ │ └── praxis
│ │ │ │ └── navigator
│ │ │ │ ├── NavigationKeys.kt
│ │ │ │ ├── NavigationCommand.kt
│ │ │ │ ├── Screens.kt
│ │ │ │ ├── Navigator.kt
│ │ │ │ └── PraxisNavigator.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── mutualmobile
│ │ │ └── praxis
│ │ │ └── navigator
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── mutualmobile
│ │ └── praxis
│ │ └── navigator
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── featcalendarview
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── praxis
│ │ │ └── feat
│ │ │ └── calendarview
│ │ │ └── ui
│ │ │ ├── CalendarYearVM.kt
│ │ │ ├── CalendarMonthVM.kt
│ │ │ ├── CalendarYearView.kt
│ │ │ └── CalendarMonthlyView.kt
│ └── test
│ │ └── java
│ │ └── com
│ │ └── praxis
│ │ └── feat
│ │ └── calendarview
│ │ ├── MainCoroutineRule.kt
│ │ └── vm
│ │ └── AuthVMTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── libjetcalendar
├── .gitignore
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── dev
│ │ └── baseio
│ │ └── libjetcalendar
│ │ ├── data
│ │ ├── JetDay.kt
│ │ ├── JetViewType.kt
│ │ ├── JetYear.kt
│ │ ├── JetMonth.kt
│ │ └── JetWeek.kt
│ │ ├── weekly
│ │ └── JetCalendarWeekView.kt
│ │ ├── monthly
│ │ └── JetCalendarMonthlyView.kt
│ │ └── yearly
│ │ └── JetCalendarYearlyView.kt
└── build.gradle.kts
├── art
├── art1.png
└── art2.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle.kts
├── .gitignore
├── team-props
├── git-hooks
│ └── pre-commit.sh
└── git-hooks.gradle.kts
├── gradle.properties
├── README.md
├── gradlew.bat
├── gradlew
└── LICENSE
/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/common/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/commonui/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/commonui/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/navigator/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/navigator/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/featcalendarview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/libjetcalendar/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/featcalendarview/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/art/art1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/art/art1.png
--------------------------------------------------------------------------------
/art/art2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/art/art2.png
--------------------------------------------------------------------------------
/app/keystore/praxis-debug.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/keystore/praxis-debug.jks
--------------------------------------------------------------------------------
/app/keystore/praxis-release.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/keystore/praxis-release.jks
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MainActivity
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/commonui/src/main/res/values/integer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 300
4 |
--------------------------------------------------------------------------------
/commonui/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/commonui/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/commonui/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/commonui/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/commonui/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/commonui/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/commonui/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/commonui/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/commonui/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oianmol/JetCalendarView/master/commonui/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/navigator/src/main/java/com/mutualmobile/praxis/navigator/NavigationKeys.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | object NavigationKeys {
4 |
5 | const val ForgotPassword = "forgotPassword"
6 | }
--------------------------------------------------------------------------------
/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/commonui/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/libjetcalendar/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/navigator/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":domain")
2 | include(":data")
3 | include(":common")
4 | include(":commonui")
5 | include(":navigator")
6 | include(":app")
7 | include(":featcalendarview")
8 | include(":libjetcalendar")
9 |
--------------------------------------------------------------------------------
/featcalendarview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /captures
8 | .externalNativeBuild
9 | .idea
10 | *.aab
11 | .cxx
12 | */build
13 | */.gradle
14 | /buildSrc/build
--------------------------------------------------------------------------------
/app/fabric.properties:
--------------------------------------------------------------------------------
1 | #Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public.
2 | #Wed Mar 08 18:34:26 IST 2017
3 | apiSecret=9cb50ff88ceece358871f08a6424267dd88f84ca25ecce34d12d8c1d9ff12367
4 |
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/data/JetDay.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.data
2 |
3 | import java.time.LocalDate
4 |
5 | open class JetCalendarType
6 |
7 | data class JetDay(val date: LocalDate, val isPartOfMonth: Boolean) : JetCalendarType()
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jul 14 15:56:55 IST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
7 |
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/AppConstants.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data
2 |
3 | object AppConstants {
4 | const val BASE_URL = "https://api.icndb.com"
5 |
6 | const val COROUTINE_RETROFIT = "COROUTINE_RETROFIT"
7 | const val RX_RETROFIT = "RX_RETROFIT"
8 | }
9 |
--------------------------------------------------------------------------------
/commonui/src/main/res/anim/slide_left_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/commonui/src/main/res/anim/slide_left_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/commonui/src/main/res/anim/slide_right_in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/commonui/src/main/res/anim/slide_right_out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/commonui/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 20dp
4 | 16sp
5 | 10sp
6 | 26sp
7 |
8 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/repository/IJokesRepo.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain.repository
2 |
3 | import com.mutualmobile.praxis.domain.SafeResult
4 | import com.mutualmobile.praxis.domain.model.DOMJokeList
5 |
6 | interface IJokesRepo {
7 | suspend fun getFiveRandomJokes(): SafeResult
8 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/mappers/DomainModel.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain.mappers
2 |
3 | open class DomainModel
4 |
5 | open class UIModel
6 |
7 | interface UiModelMapper {
8 | fun mapToPresentation(model: M): UIModel
9 |
10 | fun mapToDomain(modelItem: MI): DomainModel
11 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/mapper/EntityMapper.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.mapper
2 |
3 | import com.mutualmobile.praxis.domain.mappers.DomainModel
4 |
5 | interface EntityMapper {
6 | fun mapToDomain(entity: ME): M
7 |
8 | fun mapToEntity(model: M): ME
9 | }
10 |
11 | open class DataModel
12 |
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/data/JetViewType.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.data
2 |
3 | enum class JetViewType {
4 | MONTHLY,
5 | WEEKLY,
6 | YEARLY;
7 |
8 | fun next(): JetViewType {
9 | if (ordinal == values().size.minus(1)) {
10 | return MONTHLY
11 | }
12 | return values()[ordinal + 1]
13 | }
14 | }
--------------------------------------------------------------------------------
/commonui/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/team-props/git-hooks/pre-commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Running static analysis using ktlint..."
4 |
5 | # ktlintcheck
6 | #./gradlew ktlintcheck --daemon
7 |
8 | status=$?
9 |
10 | if [ "$status" = 0 ] ; then
11 | echo "Static analysis found no problems."
12 | exit 0
13 | else
14 | echo 1>&2 "Static analysis found violations it could not fix."
15 | exit 1
16 | fi
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/sources/IJokesRemoteSource.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.sources
2 |
3 | import com.mutualmobile.praxis.data.remote.model.NETJokeListData
4 | import com.mutualmobile.praxis.domain.SafeResult
5 |
6 | /**
7 | * Created by Vipul Asri on 13/01/21.
8 | */
9 |
10 | interface IJokesRemoteSource {
11 | suspend fun getFiveRandomJokes(): SafeResult
12 | }
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val PraxisShapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(6.dp),
10 | large = RoundedCornerShape(10.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mutualmobile/praxis/PraxisApp.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 | import timber.log.Timber
6 |
7 | @HiltAndroidApp
8 | class PraxisApp : Application() {
9 |
10 | override fun onCreate() {
11 | super.onCreate()
12 |
13 | if (BuildConfig.DEBUG) {
14 | Timber.plant(Timber.DebugTree())
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/commonui/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 | #ffffff
7 | #009688
8 | #0645AD
9 | #000
10 |
11 |
12 |
--------------------------------------------------------------------------------
/data/src/test/java/com/mutualmobile/praxis/data/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/common/src/test/java/com/mutualmobile/praxis/common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.common
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/mutualmobile/praxis/domain/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/commonui/src/test/java/com/mutualmobile/praxis/commonui/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/navigator/src/test/java/com/mutualmobile/praxis/navigator/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mutualmobile/praxis/utils/FileUtil.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.utils
2 |
3 | import java.io.BufferedReader
4 |
5 | object FileUtil {
6 |
7 | fun loadText(resourceName: String): String = readResource(resourceName).use {
8 | it.readText()
9 | }
10 |
11 | private fun readResource(resourceName: String): BufferedReader {
12 | return javaClass.classLoader!!.getResource(resourceName)
13 | .openStream()
14 | .bufferedReader()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/commonui/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/commonui/src/main/res/drawable/ic_email.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/model/DOMJoke.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain.model
2 |
3 | import android.os.Parcelable
4 | import com.mutualmobile.praxis.domain.mappers.DomainModel
5 | import kotlinx.parcelize.Parcelize
6 |
7 | /**
8 | * Created by Vipul Asri on 18/01/21.
9 | */
10 |
11 | @Parcelize
12 | data class DOMJoke(
13 | val id: Int,
14 | val joke: String
15 | ) : DomainModel(), Parcelable
16 |
17 | data class DOMJokeList(val type: String, val DOMJokes: List) : DomainModel()
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/remote/JokeApiService.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.remote
2 |
3 | import com.mutualmobile.praxis.data.remote.model.NETJokeListData
4 | import retrofit2.Retrofit
5 | import retrofit2.http.GET
6 |
7 | interface JokeApiService {
8 |
9 | companion object {
10 | fun createRetrofitService(retrofit: Retrofit): JokeApiService {
11 | return retrofit.create(JokeApiService::class.java)
12 | }
13 | }
14 |
15 | @GET("/jokes/random/5")
16 | suspend fun getFiveRandomJokes(): NETJokeListData
17 | }
--------------------------------------------------------------------------------
/app/signing.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_APPLICATION_PLUGIN)
3 | }
4 |
5 | android {
6 | signingConfigs {
7 | getByName("release") {
8 | keyAlias = "praxis-release"
9 | keyPassword = "ITHOmptI"
10 | storeFile(file("keystore/praxis-release.jks"))
11 | storePassword = "PoTHatHR"
12 | }
13 |
14 | getByName("debug") {
15 | keyAlias = "praxis-debug"
16 | keyPassword = "utherNiC"
17 | storeFile(file("keystore/praxis-debug.jks"))
18 | storePassword = "uRgeSCIt"
19 | }
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mutualmobile/praxis/TestApplication.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis
2 |
3 | import com.mutualmobile.praxis.injection.component.DaggerTestAppComponent
4 | import com.mutualmobile.praxis.injection.component.TestAppComponent
5 | import dagger.android.DaggerApplication
6 |
7 | class TestApplication : DaggerApplication() {
8 |
9 | private val component: TestAppComponent by lazy {
10 | DaggerTestAppComponent.factory()
11 | .create(this) as TestAppComponent
12 | }
13 |
14 | override fun applicationInjector() = component
15 |
16 | fun provideComponent() = component
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mutualmobile/praxis/injection/module/NavigationModule.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.injection.module
2 |
3 | import com.mutualmobile.praxis.navigator.Navigator
4 | import com.mutualmobile.praxis.navigator.PraxisNavigator
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | class NavigationModule {
14 |
15 | @Provides
16 | @Singleton
17 | fun provideNavigator(): Navigator = PraxisNavigator()
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/commonui/src/main/res/drawable/ic_eye.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/navigator/src/main/java/com/mutualmobile/praxis/navigator/NavigationCommand.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | import androidx.navigation.NavOptions
4 |
5 | sealed class NavigationCommand {
6 | object NavigateUp : NavigationCommand()
7 | data class NavigateToRoute(val route: String, val options: NavOptions? = null) :
8 | NavigationCommand()
9 |
10 | data class NavigateUpWithResult(
11 | val key: String,
12 | val result: T,
13 | val destination: String? = null
14 | ) : NavigationCommand()
15 |
16 | data class PopUpToRoute(val route: String, val inclusive: Boolean) : NavigationCommand()
17 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/injection/UseCaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain.injection
2 |
3 | import com.mutualmobile.praxis.domain.repository.IJokesRepo
4 | import com.mutualmobile.praxis.domain.usecases.GetFiveRandomJokesUseCase
5 | import dagger.Module
6 | import dagger.Provides
7 | import dagger.hilt.InstallIn
8 | import dagger.hilt.components.SingletonComponent
9 | /**
10 | * Created by Vipul Asri on 13/01/21.
11 | */
12 | @Module
13 | @InstallIn(SingletonComponent::class)
14 | object UseCaseModule {
15 |
16 | @Provides
17 | fun provideGetFiveRandomJokes(repo: IJokesRepo): GetFiveRandomJokesUseCase {
18 | return GetFiveRandomJokesUseCase(repo)
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Development/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/usecases/BaseUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain.usecases
2 |
3 | /**
4 | * Created by Vipul Asri on 13/01/21.
5 | */
6 |
7 | interface BaseUseCase {
8 |
9 | /**
10 | * Perform an operation with no input parameters.
11 | * Will throw an exception by default, if not implemented but invoked.
12 | *
13 | * @return
14 | */
15 | suspend fun perform(): Result = throw NotImplementedError()
16 |
17 | /**
18 | * Perform an operation.
19 | * Will throw an exception by default, if not implemented but invoked.
20 | *
21 | * @param params
22 | * @return
23 | */
24 | suspend fun perform(params: ExecutableParam): Result = throw NotImplementedError()
25 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/injection/SourcesModule.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.injection
2 |
3 | import com.mutualmobile.praxis.data.remote.JokeApiService
4 | import com.mutualmobile.praxis.data.sources.IJokesRemoteSource
5 | import com.mutualmobile.praxis.data.sources.JokesRemoteSource
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | /**
11 | * Created by Vipul Asri on 13/01/21.
12 | */
13 | @Module
14 | @InstallIn(SingletonComponent::class)
15 | object SourcesModule {
16 |
17 | @Provides
18 | fun provideJokesNetworkSource(apiService: JokeApiService): IJokesRemoteSource {
19 | return JokesRemoteSource(
20 | apiService
21 | )
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mutualmobile/praxis/utils/TestKotlinExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.utils
2 |
3 | import kotlinx.coroutines.runBlocking
4 | import okhttp3.mockwebserver.MockResponse
5 | import okhttp3.mockwebserver.MockWebServer
6 | import org.junit.Assert.assertThrows
7 |
8 | fun MockWebServer.enqueueResponse(
9 | responsePath: String? = null,
10 | responseCode: Int = 200
11 | ) {
12 | val mockResponse = MockResponse()
13 | .setBody(FileUtil.loadText("responses/$responsePath"))
14 | .setResponseCode(responseCode)
15 | enqueue(mockResponse)
16 | }
17 |
18 | inline fun assertThrows(
19 | noinline executable: suspend () -> Unit
20 | ) {
21 | assertThrows(T::class.java) {
22 | runBlocking {
23 | executable()
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/data/src/androidTest/java/com/mutualmobile/praxis/data/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mutualmobile.praxis.data.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/usecases/GetFiveRandomJokesUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain.usecases
2 |
3 | import com.mutualmobile.praxis.domain.SafeResult
4 | import com.mutualmobile.praxis.domain.model.DOMJoke
5 | import com.mutualmobile.praxis.domain.repository.IJokesRepo
6 |
7 | /**
8 | * Created by Vipul Asri on 13/01/21.
9 | */
10 |
11 | class GetFiveRandomJokesUseCase(private val jokesRepo: IJokesRepo) :
12 | BaseUseCase>, Unit> {
13 |
14 | override suspend fun perform(): SafeResult> {
15 | return when (val result = jokesRepo.getFiveRandomJokes()) {
16 | is SafeResult.Success -> SafeResult.Success(result.data.DOMJokes)
17 | is SafeResult.NetworkError -> result
18 | is SafeResult.Failure -> result
19 | }
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
--------------------------------------------------------------------------------
/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.
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
--------------------------------------------------------------------------------
/common/src/androidTest/java/com/mutualmobile/praxis/common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.common
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mutualmobile.praxis.common.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/commonui/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
--------------------------------------------------------------------------------
/data/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
--------------------------------------------------------------------------------
/domain/src/androidTest/java/com/mutualmobile/praxis/domain/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mutualmobile.praxis.domain.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/commonui/src/androidTest/java/com/mutualmobile/praxis/commonui/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mutualmobile.praxis.commonui.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/domain/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
--------------------------------------------------------------------------------
/navigator/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
--------------------------------------------------------------------------------
/featcalendarview/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
--------------------------------------------------------------------------------
/navigator/src/androidTest/java/com/mutualmobile/praxis/navigator/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mutualmobile.praxis.navigator.test", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/injection/RepositoryModule.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.injection
2 |
3 | import com.mutualmobile.praxis.data.remote.model.JokesListResponseMapper
4 | import com.mutualmobile.praxis.data.repository.JokesRepoImpl
5 | import com.mutualmobile.praxis.data.sources.IJokesRemoteSource
6 | import com.mutualmobile.praxis.domain.repository.IJokesRepo
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.hilt.InstallIn
10 | import dagger.hilt.components.SingletonComponent
11 |
12 | /**
13 | * Created by Vipul Asri on 13/01/21.
14 | */
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | object RepositoryModule {
18 |
19 | @Provides
20 | fun provideJokesRepository(networkSource: IJokesRemoteSource): IJokesRepo {
21 | return JokesRepoImpl(networkSource, JokesListResponseMapper())
22 | }
23 |
24 | }
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.theme
2 |
3 | import androidx.compose.material.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.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val PraxisTypography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | ),
16 | button = TextStyle(
17 | fontFamily = FontFamily.Default,
18 | fontWeight = FontWeight.W500,
19 | fontSize = 14.sp
20 | ),
21 | caption = TextStyle(
22 | fontFamily = FontFamily.Default,
23 | fontWeight = FontWeight.Normal,
24 | fontSize = 12.sp
25 | )
26 |
27 |
28 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/sources/JokesRemoteSource.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.sources
2 |
3 | import com.mutualmobile.praxis.data.remote.JokeApiService
4 | import com.mutualmobile.praxis.data.remote.model.NETJokeListData
5 | import com.mutualmobile.praxis.data.remote.safeApiCall
6 | import com.mutualmobile.praxis.domain.SafeResult
7 | import kotlinx.coroutines.CoroutineDispatcher
8 | import kotlinx.coroutines.Dispatchers
9 |
10 | /**
11 | * Created by Vipul Asri on 13/01/21.
12 | */
13 |
14 | class JokesRemoteSource(
15 | private val jokeApiService: JokeApiService,
16 | private val dispatcher: CoroutineDispatcher = Dispatchers.IO
17 | ) : IJokesRemoteSource {
18 |
19 | override suspend fun getFiveRandomJokes(): SafeResult {
20 | return safeApiCall(dispatcher) {
21 | jokeApiService.getFiveRandomJokes()
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mutualmobile/praxis/root/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.root
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.core.view.WindowCompat
7 | import com.mutualmobile.praxis.navigator.Navigator
8 | import com.mutualmobile.praxis.commonui.theme.PraxisTheme
9 | import dagger.hilt.android.AndroidEntryPoint
10 | import javax.inject.Inject
11 |
12 | @AndroidEntryPoint
13 | class MainActivity : ComponentActivity() {
14 |
15 | @Inject
16 | lateinit var navigator: Navigator
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | WindowCompat.setDecorFitsSystemWindows(window, false)
21 |
22 | setContent {
23 | PraxisTheme {
24 | PraxisNavigation(navigator)
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/commonui/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Praxis
3 | Show 5 Random Jokes (Coroutine)
4 | Show 5 Random Jokes (Rx)
5 | About
6 | Chuck Norris Random Joke Generator
7 | Praxis is a sample Android app which can be used as a base project for other projects written in Kotlin language. The app uses MVVM architecture, Architecture Components, Dagger 2 with AndroidX to provide a robust base to the app.\n\nThe Retrofit library is used to fetch jokes along with the RxJava2/Coroutines which handles connections asynchronously and makes the app more reliable.
8 | MutualMobile
9 |
10 |
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/remote/SafeApiCall.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.remote
2 |
3 | import android.util.Log
4 | import com.mutualmobile.praxis.domain.SafeResult
5 | import kotlinx.coroutines.CoroutineDispatcher
6 | import kotlinx.coroutines.withContext
7 | import retrofit2.HttpException
8 | import java.io.IOException
9 |
10 | internal suspend fun safeApiCall(
11 | dispatcher: CoroutineDispatcher,
12 | apiCall: suspend () -> T
13 | ): SafeResult {
14 | return withContext(dispatcher) {
15 | try {
16 | SafeResult.Success(apiCall.invoke())
17 | } catch (throwable: Throwable) {
18 | Log.e("safeApiCall", throwable.message.toString())
19 | when (throwable) {
20 | is IOException -> SafeResult.NetworkError
21 | is HttpException -> SafeResult.Failure(throwable)
22 | else -> SafeResult.Failure(Exception(throwable))
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/test/resources/responses/jokes_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "success",
3 | "value": [
4 | {
5 | "id": 427,
6 | "joke": "Chuck Norris' favorite cereal is Kellogg's Nails 'N' Gravel.",
7 | "categories": []
8 | },
9 | {
10 | "id": 75,
11 | "joke": "Chuck Norris can believe it's not butter.",
12 | "categories": []
13 | },
14 | {
15 | "id": 302,
16 | "joke": "Chuck Norris doesn't go on the internet, he has every internet site stored in his memory. He refreshes webpages by blinking.",
17 | "categories": []
18 | },
19 | {
20 | "id": 275,
21 | "joke": "Little Miss Muffet sat on her tuffet, until Chuck Norris roundhouse kicked her into a glacier.",
22 | "categories": []
23 | },
24 | {
25 | "id": 76,
26 | "joke": "If tapped, a Chuck Norris roundhouse kick could power the country of Australia for 44 minutes.",
27 | "categories": []
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/app/variants.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_APPLICATION_PLUGIN)
3 | }
4 | subprojects {
5 | apply {
6 | from("signing.gradle.kts")
7 | }
8 | }
9 |
10 | android {
11 | buildTypes {
12 | getByName("release") {
13 | debuggable = false
14 | versionNameSuffix = "-release"
15 |
16 | isMinifyEnabled = false
17 | shrinkResources = true
18 |
19 | proguardFiles(
20 | getDefaultProguardFile("proguard-android.txt"), "proguard-common.txt",
21 | "proguard-specific.txt"
22 | )
23 | signingConfig(
24 | signingConfigs.release
25 | // buildConfigField "boolean", "ENABLE_LOGGING", "false"
26 | )
27 | }
28 | getByName("debug") {
29 | debuggable = true
30 | versionNameSuffix = "-debug"
31 | applicationIdSuffix = ".debug"
32 | signingConfig(
33 | signingConfigs.debug
34 | // buildConfigField "boolean", "ENABLE_LOGGING", "true"
35 | )
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/featcalendarview/src/test/java/com/praxis/feat/calendarview/MainCoroutineRule.kt:
--------------------------------------------------------------------------------
1 | package com.praxis.feat.calendarview
2 |
3 | import kotlinx.coroutines.Dispatchers
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import kotlinx.coroutines.test.TestCoroutineDispatcher
6 | import kotlinx.coroutines.test.TestCoroutineScope
7 | import kotlinx.coroutines.test.resetMain
8 | import kotlinx.coroutines.test.setMain
9 | import org.junit.rules.TestWatcher
10 | import org.junit.runner.Description
11 |
12 | @ExperimentalCoroutinesApi
13 | class MainCoroutineRule(
14 | private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
15 | ) : TestWatcher(), TestCoroutineScope by TestCoroutineScope(dispatcher) {
16 | override fun starting(description: Description?) {
17 | super.starting(description)
18 | Dispatchers.setMain(dispatcher)
19 | }
20 |
21 | override fun finished(description: Description?) {
22 | super.finished(description)
23 | cleanupTestCoroutines()
24 | Dispatchers.resetMain()
25 | }
26 | }
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_PARCELABLE_PLUGIN)
5 | id(BuildPlugins.KOTLIN_KAPT)
6 | id(BuildPlugins.DAGGER_HILT)
7 | }
8 |
9 | android {
10 | compileSdk = (ProjectProperties.COMPILE_SDK)
11 |
12 | defaultConfig {
13 | minSdk = (ProjectProperties.MIN_SDK)
14 | targetSdk = (ProjectProperties.TARGET_SDK)
15 |
16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | getByName("release") {
21 | isMinifyEnabled = false
22 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
23 | }
24 | }
25 | }
26 |
27 | // Required for annotation processing plugins like Dagger
28 | kapt {
29 | generateStubs = true
30 | correctErrorTypes = true
31 | }
32 |
33 | dependencies {
34 |
35 | /*Kotlin*/
36 | api(Lib.Kotlin.KT_STD)
37 | api(Lib.Async.COROUTINES)
38 |
39 | /* Dependency Injection */
40 | api(Lib.Di.hilt)
41 | kapt(Lib.Di.hiltAndroidCompiler)
42 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/injection/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.injection
2 |
3 | import com.mutualmobile.praxis.data.AppConstants
4 | import com.mutualmobile.praxis.data.remote.JokeApiService
5 | import com.mutualmobile.praxis.data.remote.RetrofitHelper
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.InstallIn
9 | import dagger.hilt.components.SingletonComponent
10 | import okhttp3.OkHttpClient
11 | import retrofit2.Retrofit
12 | /**
13 | * Created by Vipul Asri on 13/01/21.
14 | */
15 |
16 | @Module
17 | @InstallIn(SingletonComponent::class)
18 | object NetworkModule {
19 |
20 | @Provides
21 | fun provideHttpClient(): OkHttpClient {
22 | return RetrofitHelper.createOkHttpClient()
23 | }
24 |
25 | @Provides
26 | fun provideRetrofit(
27 | okHttpClient: OkHttpClient
28 | ): Retrofit {
29 | return RetrofitHelper.createRetrofitClient(okHttpClient, AppConstants.BASE_URL)
30 | }
31 |
32 | @Provides
33 | fun provideJokesApiService(retrofit: Retrofit): JokeApiService {
34 | return JokeApiService.createRetrofitService(retrofit)
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/team-props/git-hooks.gradle.kts:
--------------------------------------------------------------------------------
1 | fun isLinuxOrMacOs(): Boolean {
2 | val osName = System.getProperty("os.name")
3 | .toLowerCase()
4 | return osName.contains("linux") || osName.contains("mac os") || osName.contains("macos")
5 | }
6 |
7 | tasks.create("copyGitHooks") {
8 | description = "Copies the git hooks from team-props/git-hooks to the .git folder."
9 | from("$rootDir/team-props/git-hooks/") {
10 | include("**/*.sh")
11 | rename("(.*).sh", "$1")
12 | }
13 | into("$rootDir/.git/hooks")
14 | onlyIf { isLinuxOrMacOs() }
15 | }
16 |
17 | tasks.create("installGitHooks") {
18 | description = "Installs the pre-commit git hooks from team-props/git-hooks."
19 | group = "git hooks"
20 | workingDir(rootDir)
21 | commandLine("chmod")
22 | args("-R", "+x", ".git/hooks/")
23 | dependsOn("copyGitHooks")
24 | onlyIf { isLinuxOrMacOs() }
25 | doLast {
26 | logger.info("Git hook installed successfully.")
27 | }
28 | }
29 |
30 | tasks.getByName("installGitHooks")
31 | .dependsOn(getTasksByName("copyGitHooks", true))
32 | tasks.getByPath("app:preBuild")
33 | .dependsOn(getTasksByName("installGitHooks", true))
34 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/remote/model/NETJokeListData.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.remote.model
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import com.mutualmobile.praxis.data.mapper.EntityMapper
5 | import com.mutualmobile.praxis.data.mapper.DataModel
6 | import com.mutualmobile.praxis.domain.model.DOMJoke
7 | import com.mutualmobile.praxis.domain.model.DOMJokeList
8 |
9 | data class NETJokeListData(
10 | @SerializedName("type")
11 | val type: String,
12 | @SerializedName("value")
13 | val value: List
14 | ) : DataModel()
15 |
16 | data class NETJokeData(
17 | @SerializedName("id")
18 | val id: Int,
19 | @SerializedName("joke")
20 | val joke: String
21 | ) : DataModel()
22 |
23 | class JokesListResponseMapper : EntityMapper {
24 | override fun mapToDomain(entity: NETJokeListData): DOMJokeList {
25 | return DOMJokeList(entity.type, entity.value.map { DOMJoke(it.id, it.joke) })
26 | }
27 |
28 | override fun mapToEntity(model: DOMJokeList): NETJokeListData {
29 | return NETJokeListData(model.type, model.DOMJokes.map { NETJokeData(it.id, it.joke) })
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/remote/RetrofitHelper.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.remote
2 |
3 | import com.mutualmobile.praxis.data.BuildConfig
4 | import okhttp3.OkHttpClient
5 | import okhttp3.logging.HttpLoggingInterceptor
6 | import retrofit2.Retrofit
7 | import retrofit2.converter.gson.GsonConverterFactory
8 |
9 | object RetrofitHelper {
10 |
11 | private val okHttpLoggingInterceptor by lazy {
12 | HttpLoggingInterceptor().apply {
13 | level =
14 | if (BuildConfig.DEBUG)
15 | HttpLoggingInterceptor.Level.BODY
16 | else
17 | HttpLoggingInterceptor.Level.NONE
18 | }
19 | }
20 |
21 | fun createOkHttpClient(): OkHttpClient {
22 | return OkHttpClient.Builder()
23 | .addInterceptor(okHttpLoggingInterceptor)
24 | .retryOnConnectionFailure(true)
25 | .build()
26 | }
27 |
28 | fun createRetrofitClient(
29 | okHttpClient: OkHttpClient,
30 | baseUrl: String
31 | ): Retrofit {
32 | return Retrofit.Builder()
33 | .baseUrl(baseUrl)
34 | .addConverterFactory(GsonConverterFactory.create())
35 | .client(okHttpClient)
36 | .build()
37 | }
38 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JetCalendarView
2 |
3 | Hit Refresh! Calendar view ❤️ Jetpack Compose
4 |
5 | This project is an experimental calendar view. Please raise issues if you find any, thanks!
6 |
7 | ## Features
8 |
9 | - Year View (List and Grid View) with optional Navigation buttons
10 | - Month View with optional Navigation buttons
11 | - Modular and built with a week as a simplest view
12 |
13 |
14 |
15 |
16 |
17 |
18 | License
19 | =======
20 | Copyright 2022 Anmol Verma
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 |
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val White = Color(0xffffffff)
6 | val PraxisColor = Color(0xff00ACEE)
7 |
8 | val VeryLightGrey = Color(0xffdadce0)
9 | val LightGrey = Color(0xffe8eaed)
10 | val Grey = Color(0xff5f6368)
11 | val TextPrimary = Color(0xff202124)
12 | val TextSecondaryDark = Color(0xff212121)
13 | val TextSecondary = Color(0xff5f6368)
14 | val DarkGreen = Color(0xff056449)
15 | val GreyBg = Color(0xff15202b)
16 | val SearchBarDarkColor = Color(0xff10171e)
17 |
18 | val Shadow1 = Color(0xffded6fe)
19 |
20 | val Ocean11 = Color(0xff005687)
21 | val Ocean2 = Color(0xffbbfdfd)
22 |
23 | val Neutral7 = Color(0xff000000)
24 | val Neutral6 = Color(0x99000000)
25 | val Neutral3 = Color(0x1fffffff)
26 | val Neutral2 = Color(0x61ffffff)
27 | val Neutral1 = Color(0xbdffffff)
28 | val Neutral0 = Color(0xffffffff)
29 |
30 | val FunctionalRed = Color(0xffd00036)
31 | val FunctionalRedDark = Color(0xffea6d7e)
32 | val FunctionalGrey = Color(0xfff6f6f6)
33 | val FunctionalDarkGrey = Color(0xff2e2e2e)
34 |
35 | const val AlphaNearOpaque = 0.95f
36 | const val AlphaNearTransparent = 0.15f
--------------------------------------------------------------------------------
/data/src/main/java/com/mutualmobile/praxis/data/repository/JokesRepoImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.data.repository
2 |
3 | import com.mutualmobile.praxis.data.mapper.EntityMapper
4 | import com.mutualmobile.praxis.data.remote.model.NETJokeListData
5 | import com.mutualmobile.praxis.data.sources.IJokesRemoteSource
6 | import com.mutualmobile.praxis.domain.SafeResult
7 | import com.mutualmobile.praxis.domain.model.DOMJokeList
8 | import com.mutualmobile.praxis.domain.repository.IJokesRepo
9 | import java.lang.RuntimeException
10 |
11 | /**
12 | * Created by Vipul Asri on 13/01/21.
13 | */
14 |
15 | class JokesRepoImpl(
16 | private val remoteSource: IJokesRemoteSource,
17 | private val DOMJokeListResponseMapper: EntityMapper
18 | ) : IJokesRepo {
19 |
20 | override suspend fun getFiveRandomJokes(): SafeResult {
21 | return when (val result = remoteSource.getFiveRandomJokes()) {
22 | is SafeResult.Success<*> -> SafeResult.Success(DOMJokeListResponseMapper.mapToDomain(result.data as NETJokeListData))
23 | is SafeResult.Failure -> SafeResult.Failure(result.exception)
24 | SafeResult.NetworkError -> SafeResult.NetworkError
25 | else -> { throw RuntimeException()}
26 | }
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_KAPT)
5 | id(BuildPlugins.DAGGER_HILT)
6 | }
7 |
8 | android {
9 | compileSdk = ProjectProperties.COMPILE_SDK
10 |
11 | defaultConfig {
12 | minSdk = (ProjectProperties.MIN_SDK)
13 | targetSdk = (ProjectProperties.TARGET_SDK)
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | getByName("release") {
19 | isMinifyEnabled = false
20 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
21 | }
22 | }
23 | }
24 |
25 | // Required for annotation processing plugins like Dagger
26 | kapt {
27 | generateStubs = true
28 | correctErrorTypes = true
29 | }
30 |
31 | dependencies {
32 |
33 | implementation(project(":domain"))
34 | /*Kotlin*/
35 | api(Lib.Kotlin.KT_STD)
36 | api(Lib.Async.COROUTINES)
37 |
38 | /* Networking */
39 | api(Lib.Networking.RETROFIT)
40 | api(Lib.Networking.RETROFIT_GSON)
41 | api(Lib.Networking.LOGGING)
42 |
43 | api(Lib.Serialization.GSON)
44 |
45 | /* Dependency Injection */
46 | api(Lib.Di.hilt)
47 | kapt(Lib.Di.hiltAndroidCompiler)
48 | }
--------------------------------------------------------------------------------
/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_KAPT)
5 | id(BuildPlugins.DAGGER_HILT)
6 | }
7 |
8 | android {
9 | compileSdk = ProjectProperties.COMPILE_SDK
10 |
11 | defaultConfig {
12 | minSdk = (ProjectProperties.MIN_SDK)
13 | targetSdk = (ProjectProperties.TARGET_SDK)
14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | buildTypes {
18 | getByName("release") {
19 | isMinifyEnabled = false
20 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
21 | }
22 | }
23 | }
24 |
25 | // Required for annotation processing plugins like Dagger
26 | kapt {
27 | generateStubs = true
28 | correctErrorTypes = true
29 | }
30 |
31 | dependencies {
32 | /*Kotlin*/
33 | api(Lib.Kotlin.KT_STD)
34 |
35 | /* Dependency Injection */
36 | api(Lib.Di.hilt)
37 | api(Lib.Di.hiltNavigationCompose)
38 | api(Lib.Di.viewmodel)
39 |
40 | kapt(Lib.Di.hiltCompiler)
41 | kaptTest(Lib.Di.hiltCompiler)
42 | kapt(Lib.Di.hiltAndroidCompiler)
43 | kaptTest(Lib.Di.hiltAndroidCompiler)
44 |
45 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mutualmobile/praxis/useCaseTest/GetFiveRandomJokesUseCaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.useCaseTest
2 |
3 | import com.mutualmobile.praxis.base.BaseTest
4 | import com.mutualmobile.praxis.data.SafeResult
5 | import com.mutualmobile.praxis.domain.usecases.GetFiveRandomJokesUseCase
6 | import com.mutualmobile.praxis.injection.component.TestAppComponent
7 | import com.mutualmobile.praxis.utils.enqueueResponse
8 | import junit.framework.TestCase.assertEquals
9 | import kotlinx.coroutines.runBlocking
10 | import org.junit.Test
11 | import javax.inject.Inject
12 |
13 | class GetFiveRandomJokesUseCaseTest : BaseTest() {
14 |
15 | override fun injectIntoDagger(testAppComponent: TestAppComponent) {
16 | testAppComponent.inject(this)
17 | }
18 |
19 | @Inject
20 | lateinit var getFiveRandomJokesUseCase: GetFiveRandomJokesUseCase
21 |
22 | @Test
23 | fun `when api returns success- assert result data contains Jokes`() =
24 | runBlocking {
25 | mockWebServer.enqueueResponse("jokes_response.json")
26 |
27 | val result = getFiveRandomJokesUseCase.perform()
28 | assertEquals(1, mockWebServer.requestCount)
29 | assert(result is SafeResult.Success)
30 | assert((result as SafeResult.Success).data.isNotEmpty())
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mutualmobile/praxis/base/BaseTest.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.base
2 |
3 | import android.os.Build
4 | import androidx.test.core.app.ApplicationProvider
5 | import androidx.test.ext.junit.runners.AndroidJUnit4
6 | import com.mutualmobile.praxis.TestApplication
7 | import com.mutualmobile.praxis.injection.component.TestAppComponent
8 | import okhttp3.mockwebserver.MockWebServer
9 | import org.junit.After
10 | import org.junit.Before
11 | import org.junit.runner.RunWith
12 | import org.robolectric.annotation.Config
13 | import javax.inject.Inject
14 |
15 | /**
16 | * Created by Ashish Suman on 09/03/21
17 | */
18 |
19 | @RunWith(AndroidJUnit4::class)
20 | @Config(application = TestApplication::class, sdk = [Build.VERSION_CODES.O_MR1])
21 | abstract class BaseTest {
22 |
23 | @Inject
24 | lateinit var mockWebServer: MockWebServer
25 |
26 | private lateinit var application: TestApplication
27 |
28 | @Before
29 | open fun setup() {
30 | application = ApplicationProvider.getApplicationContext() as TestApplication
31 | injectIntoDagger(application.provideComponent())
32 | }
33 |
34 | @After
35 | open fun tearDown() {
36 | mockWebServer.shutdown()
37 | }
38 |
39 | abstract fun injectIntoDagger(testAppComponent: TestAppComponent)
40 |
41 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/mutualmobile/praxis/domain/SafeResult.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.domain
2 |
3 | /**
4 | * A generic class that holds a value with its loading status.
5 | * @param
6 | */
7 | sealed class SafeResult {
8 |
9 | data class Success(val data: T) : SafeResult()
10 | data class Failure(
11 | val exception: Exception? = Exception("Unknown Error"),
12 | val message: String = exception?.localizedMessage ?: ""
13 | ) : SafeResult()
14 |
15 | object NetworkError : SafeResult()
16 |
17 | override fun toString(): String {
18 | return when (this) {
19 | is Success -> "Success[data=$data]"
20 | is Failure -> "Failure[exception=$exception]"
21 | is NetworkError -> "NetworkError"
22 | }
23 | }
24 | }
25 |
26 | /**
27 | * `true` if [SafeResult] is of type [Success] & holds non-null [Success.data].
28 | */
29 | val SafeResult<*>.succeeded
30 | get() = this is SafeResult.Success && data != null
31 |
32 | fun SafeResult.getSuccessOrNull(): T? {
33 | return when (this) {
34 | is SafeResult.Success -> this.data
35 | else -> null
36 | }
37 | }
38 |
39 | fun SafeResult.getErrorOrNull(): SafeResult.Failure? {
40 | return when (this) {
41 | is SafeResult.Failure -> this
42 | else -> null
43 | }
44 | }
--------------------------------------------------------------------------------
/navigator/src/main/java/com/mutualmobile/praxis/navigator/Screens.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | import androidx.navigation.NamedNavArgument
4 | import androidx.navigation.NavType
5 | import androidx.navigation.navArgument
6 |
7 | sealed class Screen(
8 | baseRoute: String,
9 | val navArguments: List = emptyList()
10 | ) {
11 | val route: String = baseRoute.appendArguments(navArguments)
12 |
13 | object CalendarYearRoute :
14 | Screen("yearView")
15 |
16 | object CalendarMonthRoute :
17 | Screen(
18 | "monthView",
19 | navArguments = listOf(navArgument("date") { type = NavType.LongType })
20 | ) {
21 | fun createRoute(date: Long) =
22 | route.replace("{${navArguments.first().name}}","$date")
23 | }
24 | }
25 |
26 | private fun String.appendArguments(navArguments: List): String {
27 | val mandatoryArguments = navArguments.filter { it.argument.defaultValue == null }
28 | .takeIf { it.isNotEmpty() }
29 | ?.joinToString(separator = "/", prefix = "/") { "{${it.name}}" }
30 | .orEmpty()
31 | val optionalArguments = navArguments.filter { it.argument.defaultValue != null }
32 | .takeIf { it.isNotEmpty() }
33 | ?.joinToString(separator = "&", prefix = "?") { "${it.name}={${it.name}}" }
34 | .orEmpty()
35 | return "$this$mandatoryArguments$optionalArguments"
36 | }
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/material/CommonTopAppBar.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.material
2 |
3 | import androidx.compose.foundation.layout.RowScope
4 | import androidx.compose.material.*
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.unit.Dp
9 | import androidx.compose.ui.unit.dp
10 | import com.mutualmobile.praxis.commonui.theme.PraxisSurface
11 | import com.mutualmobile.praxis.commonui.theme.PraxisTheme
12 |
13 | @Composable
14 | fun CommonTopAppBar(
15 | title: @Composable () -> Unit,
16 | modifier: Modifier = Modifier,
17 | navigationIcon: @Composable (() -> Unit)? = null,
18 | actions: @Composable RowScope.() -> Unit = {},
19 | backgroundColor: Color = PraxisTheme.colors.uiBackground,
20 | contentColor: Color = contentColorFor(backgroundColor),
21 | elevation: Dp = AppBarDefaults.TopAppBarElevation
22 | ) {
23 | PraxisSurface(
24 | color = PraxisTheme.colors.uiBackground,
25 | contentColor = PraxisTheme.colors.accent,
26 | elevation = 4.dp
27 | ) {
28 | TopAppBar(
29 | modifier = modifier,
30 | contentColor = contentColor,
31 | elevation = elevation,
32 | title = title,
33 | navigationIcon = navigationIcon,
34 | actions = actions,
35 | backgroundColor = backgroundColor,
36 | )
37 | }
38 | }
--------------------------------------------------------------------------------
/navigator/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_KAPT)
5 | }
6 |
7 | android {
8 | compileSdk = ProjectProperties.COMPILE_SDK
9 |
10 | defaultConfig {
11 | minSdk = (ProjectProperties.MIN_SDK)
12 | targetSdk = (ProjectProperties.TARGET_SDK)
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | buildTypes {
17 | getByName("release") {
18 | isMinifyEnabled = false
19 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
20 | }
21 | }
22 |
23 |
24 | compileOptions {
25 | sourceCompatibility = JavaVersion.VERSION_1_8
26 | targetCompatibility = JavaVersion.VERSION_1_8
27 | }
28 |
29 | kotlinOptions {
30 | jvmTarget = "1.8"
31 | }
32 |
33 |
34 | composeOptions {
35 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER
36 | }
37 | }
38 |
39 | // Required for annotation processing plugins like Dagger
40 | kapt {
41 | generateStubs = true
42 | correctErrorTypes = true
43 | }
44 |
45 | dependencies {
46 | /*Kotlin*/
47 | implementation(Lib.Android.appCompat)
48 | implementation(Lib.Kotlin.KTX_CORE)
49 | api(Lib.Async.COROUTINES)
50 | api(Lib.Async.COROUTINES_ANDROID)
51 |
52 |
53 | implementation(Lib.Kotlin.KT_STD)
54 | implementation(Lib.Android.navigationCompose)
55 |
56 | implementation(Lib.Android.navigationCompose)
57 | implementation(Lib.Di.hiltNavigationCompose)
58 |
59 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/mutualmobile/praxis/injection/module/FakeNetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.injection.module
2 |
3 | import com.mutualmobile.praxis.data.remote.JokeApiService
4 | import com.mutualmobile.praxis.data.remote.RetrofitHelper
5 | import dagger.Module
6 | import dagger.Provides
7 | import okhttp3.OkHttpClient
8 | import okhttp3.mockwebserver.MockWebServer
9 | import retrofit2.Retrofit
10 | import javax.inject.Named
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | class FakeNetworkModule {
15 |
16 | @Provides
17 | @Singleton
18 | internal fun provideMockWebServer(): MockWebServer {
19 | var mockWebServer: MockWebServer? = null
20 | val thread = Thread {
21 | mockWebServer = MockWebServer()
22 | mockWebServer?.start()
23 | }
24 | thread.start()
25 | thread.join()
26 | return mockWebServer!!
27 | }
28 |
29 | @Provides
30 | @Singleton
31 | @Named("mockRootUrl")
32 | internal fun provideBaseUrl(mockWebServer: MockWebServer): String {
33 | return mockWebServer.url("/")
34 | .toString()
35 | }
36 |
37 | @Provides
38 | @Singleton
39 | internal fun provideHttpClient(): OkHttpClient {
40 | return RetrofitHelper.createOkHttpClient()
41 | }
42 |
43 | @Provides
44 | @Singleton
45 | internal fun provideRetrofit(
46 | okHttpClient: OkHttpClient,
47 | @Named("mockRootUrl") rootUrl: String
48 | ): Retrofit {
49 | return RetrofitHelper.createRetrofitClient(okHttpClient, rootUrl)
50 | }
51 |
52 | @Provides
53 | @Singleton
54 | internal fun provideJokesApiService(retrofit: Retrofit): JokeApiService {
55 | return JokeApiService.createRetrofitService(retrofit)
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/material/DefaultSnackbar.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.material
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.layout.wrapContentHeight
6 | import androidx.compose.material.*
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.unit.dp
11 | import com.mutualmobile.praxis.commonui.theme.PraxisTheme
12 | import com.mutualmobile.praxis.commonui.theme.PraxisTypography
13 |
14 | @Composable
15 | fun DefaultSnackbar(
16 | snackbarHostState: SnackbarHostState,
17 | modifier: Modifier = Modifier,
18 | onDismiss: () -> Unit = { }
19 | ) {
20 | SnackbarHost(
21 | hostState = snackbarHostState,
22 | snackbar = { data ->
23 | Snackbar(
24 | content = {
25 | Text(
26 | text = data.message,
27 | style = PraxisTypography.body1,
28 | color = PraxisTheme.colors.textPrimary,
29 | )
30 | },
31 | action = {
32 | data.actionLabel?.let { actionLabel ->
33 | TextButton(onClick = onDismiss) {
34 | Text(
35 | text = actionLabel,
36 | color = PraxisTheme.colors.textPrimary,
37 | style = PraxisTypography.body2
38 | )
39 | }
40 | }
41 | },
42 | backgroundColor = PraxisTheme.colors.accent
43 | )
44 | },
45 | modifier = modifier
46 | .fillMaxWidth()
47 | .wrapContentHeight(Alignment.Bottom)
48 | )
49 | }
--------------------------------------------------------------------------------
/commonui/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_KAPT)
5 | }
6 |
7 | android {
8 | compileSdk = ProjectProperties.COMPILE_SDK
9 |
10 | defaultConfig {
11 | minSdk = (ProjectProperties.MIN_SDK)
12 | targetSdk = (ProjectProperties.TARGET_SDK)
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | buildTypes {
17 | getByName("release") {
18 | isMinifyEnabled = false
19 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
20 | }
21 | }
22 | buildFeatures {
23 | compose = true
24 | }
25 |
26 | composeOptions {
27 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER
28 | }
29 | packagingOptions {
30 | resources {
31 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
32 | }
33 | }
34 |
35 | }
36 |
37 | // Required for annotation processing plugins like Dagger
38 | kapt {
39 | generateStubs = true
40 | correctErrorTypes = true
41 | }
42 |
43 | dependencies {
44 | /*Kotlin*/
45 | api(Lib.Kotlin.KT_STD)
46 | api(Lib.Kotlin.KTX_CORE)
47 | /* Android Designing and layout */
48 | api(Lib.Android.MATERIAL_DESIGN)
49 | api(Lib.Android.COMPOSE_UI)
50 | api(Lib.Android.COIL_COMPOSE)
51 | api(Lib.Android.COMPOSE_MATERIAL)
52 | api(Lib.Android.COMPOSE_TOOLING)
53 | debugApi(Lib.Android.DEBUG_TOOLING)
54 | api(Lib.Android.ACT_COMPOSE)
55 |
56 | /* Dependency Injection */
57 | api(Lib.Di.hilt)
58 | kapt(Lib.Di.hiltAndroidCompiler)
59 |
60 |
61 | }
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/data/JetYear.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.data
2 |
3 | import java.time.DayOfWeek
4 | import java.time.LocalDate
5 | import java.time.temporal.TemporalAdjusters
6 | import java.time.temporal.WeekFields
7 | import java.util.*
8 |
9 |
10 | class JetYear private constructor(
11 | val startDate: LocalDate,
12 | val endDate: LocalDate,
13 | ) : JetCalendarType() {
14 | lateinit var yearMonths: List
15 |
16 | companion object {
17 | fun current(
18 | date: LocalDate = LocalDate.now(),
19 | firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
20 | ): JetYear {
21 | val day: LocalDate = date.with(TemporalAdjusters.firstDayOfYear())
22 | val last: LocalDate = date.with(TemporalAdjusters.lastDayOfYear())
23 | val year = JetYear(day, last)
24 | year.yearMonths = year.months(firstDayOfWeek)
25 | return year
26 | }
27 | }
28 |
29 | private fun months(firstDayOfWeek: DayOfWeek): List {
30 | val months = mutableListOf()
31 |
32 | var startDateMonth = this.startDate.withDayOfMonth(1)
33 | var endDateMonth = this.startDate.withDayOfMonth(this.startDate.lengthOfMonth())
34 |
35 | var currentYear = this.startDate.year
36 | while (true) {
37 | months.add(JetMonth.current(startDateMonth, firstDayOfWeek))
38 |
39 | startDateMonth = endDateMonth.plusDays(1)
40 | endDateMonth = startDateMonth.withDayOfMonth(startDateMonth.lengthOfMonth())
41 | if (endDateMonth.year > currentYear) {
42 | break
43 | }
44 | currentYear = endDateMonth.year
45 | }
46 | return months
47 | }
48 |
49 | fun year(): String {
50 | return this.startDate.year.toString()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/libjetcalendar/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_KAPT)
5 | }
6 |
7 | android {
8 | compileSdk = ProjectProperties.COMPILE_SDK
9 |
10 | defaultConfig {
11 | minSdk = (ProjectProperties.MIN_SDK)
12 | targetSdk = (ProjectProperties.TARGET_SDK)
13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
14 | }
15 |
16 | buildFeatures {
17 | compose = true
18 | }
19 |
20 | composeOptions {
21 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER
22 | }
23 | packagingOptions {
24 | resources.excludes.add("META-INF/LICENSE.txt")
25 | resources.excludes.add("META-INF/NOTICE.txt")
26 | resources.excludes.add("LICENSE.txt")
27 | resources.excludes.add( "/META-INF/{AL2.0,LGPL2.1}")
28 | }
29 |
30 | compileOptions {
31 | isCoreLibraryDesugaringEnabled = true
32 | sourceCompatibility = JavaVersion.VERSION_1_8
33 | targetCompatibility = JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = "1.8"
38 | }
39 |
40 | }
41 |
42 | // Required for annotation processing plugins like Dagger
43 | kapt {
44 | generateStubs = true
45 | correctErrorTypes = true
46 | }
47 |
48 | dependencies {
49 | /*Kotlin*/
50 | coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
51 |
52 | api(Lib.Android.COMPOSE_UI)
53 | api(Lib.Android.COMPOSE_MATERIAL)
54 | api(Lib.Android.COMPOSE_TOOLING)
55 | debugApi(Lib.Android.DEBUG_TOOLING)
56 | api(Lib.Android.ACT_COMPOSE)
57 | api(Lib.Android.ACCOMPANIST_INSETS)
58 |
59 | api(Lib.Android.appCompat)
60 | api(Lib.Kotlin.KTX_CORE)
61 |
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mutualmobile/praxis/root/PraxisNavigation.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.root
2 |
3 | import androidx.compose.foundation.layout.fillMaxSize
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.runtime.LaunchedEffect
6 | import androidx.compose.ui.Modifier
7 | import androidx.navigation.compose.NavHost
8 | import androidx.navigation.compose.composable
9 | import androidx.navigation.compose.rememberNavController
10 | import com.google.accompanist.insets.ProvideWindowInsets
11 | import com.mutualmobile.praxis.commonui.theme.AlphaNearOpaque
12 | import com.mutualmobile.praxis.commonui.theme.PraxisSurface
13 | import com.mutualmobile.praxis.commonui.theme.PraxisTheme
14 | import com.mutualmobile.praxis.navigator.Navigator
15 | import com.mutualmobile.praxis.navigator.Screen
16 | import com.praxis.feat.calendarview.ui.CalendarMonthlyView
17 | import com.praxis.feat.calendarview.ui.CalendarYearView
18 |
19 | @Composable
20 | fun PraxisNavigation(navigator: Navigator) {
21 | ProvideWindowInsets {
22 | PraxisSurface(
23 | color = PraxisTheme.colors.statusBarColor.copy(alpha = AlphaNearOpaque),
24 | modifier = Modifier.fillMaxSize()
25 | ) {
26 | val navController = rememberNavController()
27 |
28 | LaunchedEffect(Unit) {
29 | navigator.handleNavigationCommands(navController)
30 | }
31 |
32 | NavHost(
33 | navController = navController,
34 | startDestination = Screen.CalendarYearRoute.route
35 | ) {
36 | composable(Screen.CalendarYearRoute.route) {
37 | CalendarYearView()
38 | }
39 | composable(
40 | Screen.CalendarMonthRoute.route, arguments = Screen.CalendarMonthRoute.navArguments
41 | ) {
42 | CalendarMonthlyView()
43 | }
44 | }
45 | }
46 | }
47 |
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/navigator/src/main/java/com/mutualmobile/praxis/navigator/Navigator.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.Observer
5 | import androidx.navigation.NavController
6 | import androidx.navigation.NavOptions
7 | import androidx.navigation.NavOptionsBuilder
8 | import androidx.navigation.navOptions
9 | import kotlinx.coroutines.Dispatchers
10 | import kotlinx.coroutines.GlobalScope
11 | import kotlinx.coroutines.channels.Channel
12 | import kotlinx.coroutines.flow.*
13 | import kotlinx.coroutines.launch
14 | import kotlinx.coroutines.withContext
15 |
16 | abstract class Navigator {
17 | // We use a StateFlow here to allow ViewModels to start observing navigation results before the initial composition,
18 | // and still get the navigation result later
19 | val navControllerFlow = MutableStateFlow(null)
20 |
21 | val navigationCommands =
22 | MutableSharedFlow(extraBufferCapacity = Int.MAX_VALUE)
23 |
24 |
25 | abstract suspend fun handleNavigationCommands(navController: NavController)
26 | abstract fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)? = null)
27 | abstract fun navigateAndClearBackStack(route: String)
28 | abstract fun navigateUp()
29 | abstract fun popUpTo(route: String, inclusive: Boolean)
30 | abstract fun navigateBackWithResult(key: String, result: T, destination: String?)
31 | abstract fun observeResult(key: String, route: String? = null): Flow
32 | }
33 |
34 |
35 |
36 | fun LiveData.asFlow(): Flow = flow {
37 | val channel = Channel(Channel.CONFLATED)
38 | val observer = Observer {
39 | channel.trySend(it)
40 | }
41 | withContext(Dispatchers.Main.immediate) {
42 | observeForever(observer)
43 | }
44 | try {
45 | for (value in channel) {
46 | emit(value)
47 | }
48 | } finally {
49 | GlobalScope.launch(Dispatchers.Main.immediate) {
50 | removeObserver(observer)
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/featcalendarview/src/test/java/com/praxis/feat/calendarview/vm/AuthVMTest.kt:
--------------------------------------------------------------------------------
1 | package com.praxis.feat.calendarview.vm
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import com.mutualmobile.praxis.navigator.Navigator
5 | import com.mutualmobile.praxis.navigator.PraxisNavigator
6 | import com.praxis.feat.calendarview.MainCoroutineRule
7 | import com.praxis.feat.calendarview.ui.model.LoginForm
8 | import io.mockk.coEvery
9 | import io.mockk.mockk
10 | import kotlinx.coroutines.runBlocking
11 | import org.junit.Before
12 | import org.junit.Rule
13 | import org.junit.Test
14 |
15 | class AuthVMTest {
16 |
17 | private var navigator: Navigator = PraxisNavigator()
18 | private lateinit var savedStateHandle: SavedStateHandle
19 | private lateinit var authVM: AuthVM
20 |
21 | @get:Rule
22 | val coroutineRule = MainCoroutineRule()
23 |
24 |
25 | @Before
26 | fun setUp() {
27 | savedStateHandle = mockk()
28 | }
29 |
30 | @Test
31 | fun `test that login now fails with validation exception`() {
32 | runBlocking {
33 |
34 | coEvery {
35 | savedStateHandle.get(any())
36 | } returns ""
37 |
38 | authVM = AuthVM(savedStateHandle, navigator)
39 |
40 | assert(authVM.uiState.value is AuthVM.UiState.Empty)
41 | authVM.loginNow()
42 | assert(authVM.uiState.value is AuthVM.UiState.ErrorState)
43 | }
44 | }
45 |
46 | @Test
47 | fun `test that loginNow() completes with no exception`() {
48 | runBlocking {
49 |
50 | coEvery {
51 | savedStateHandle.get(any())
52 | } returns ""
53 |
54 | authVM = AuthVM(savedStateHandle, navigator)
55 | authVM.credentials.value = LoginForm("anmol@gmail.com","sdkfkjkjfdsjkfds")
56 | assert(authVM.uiState.value is AuthVM.UiState.Empty)
57 | authVM.loginNow()
58 | assert(authVM.uiState.value is AuthVM.UiState.SuccessState)
59 | }
60 | }
61 |
62 | @Test
63 | fun `navigateForgotPassword how ?`() {
64 | TODO("how do we test navigation controller in unit test ?")
65 | }
66 | }
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/data/JetMonth.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.data
2 |
3 | import java.time.DayOfWeek
4 | import java.time.LocalDate
5 | import java.time.YearMonth
6 | import java.time.format.TextStyle
7 | import java.time.temporal.TemporalAdjusters
8 | import java.time.temporal.WeekFields
9 | import java.util.*
10 |
11 |
12 | class JetMonth private constructor(
13 | val startDate: LocalDate,
14 | val endDate: LocalDate,
15 | var firstDayOfWeek: DayOfWeek,
16 | ) : JetCalendarType() {
17 | lateinit var monthWeeks: List
18 |
19 | fun name(): String {
20 | return startDate.month.getDisplayName(
21 | TextStyle.SHORT,
22 | Locale.getDefault()
23 | )
24 | }
25 |
26 | fun monthYear(): String {
27 | return name() + " " + year()
28 | }
29 |
30 | fun year(): String {
31 | return startDate.year.toString()
32 | }
33 |
34 | companion object {
35 | fun current(
36 | date: LocalDate = LocalDate.now(),
37 | firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
38 | ): JetMonth {
39 | val startOfMonth = date.with(TemporalAdjusters.firstDayOfMonth())
40 | val endOfMonth = date.with(TemporalAdjusters.lastDayOfMonth())
41 | val month = JetMonth(startOfMonth, endOfMonth, firstDayOfWeek = firstDayOfWeek)
42 | month.monthWeeks = month.weeks(firstDayOfWeek)
43 | return month
44 | }
45 | }
46 |
47 | private fun weeks(firstDayOfWeek: DayOfWeek): List {
48 | val currentYearMonth: YearMonth = YearMonth.of(this.endDate.year, this.endDate.monthValue)
49 | val weeks = currentYearMonth.atEndOfMonth().get(WeekFields.of(firstDayOfWeek, 1).weekOfMonth())
50 | val monthWeeks = mutableListOf()
51 | monthWeeks.add(
52 | JetWeek.current(
53 | startDate,
54 | dayOfWeek = this.firstDayOfWeek
55 | )
56 | )
57 | while (monthWeeks.size != weeks) {
58 | monthWeeks.add(monthWeeks.last().nextWeek())
59 | }
60 | return monthWeeks
61 | }
62 |
63 | }
64 |
65 |
66 |
--------------------------------------------------------------------------------
/featcalendarview/src/main/java/com/praxis/feat/calendarview/ui/CalendarYearVM.kt:
--------------------------------------------------------------------------------
1 | package com.praxis.feat.calendarview.ui
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.viewModelScope
5 | import com.mutualmobile.praxis.navigator.Navigator
6 | import com.mutualmobile.praxis.navigator.Screen
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import dev.baseio.libjetcalendar.data.JetDay
9 | import dev.baseio.libjetcalendar.data.JetYear
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.flow.MutableStateFlow
12 | import kotlinx.coroutines.launch
13 | import kotlinx.coroutines.withContext
14 | import java.time.DayOfWeek
15 | import java.time.LocalDate
16 | import java.time.temporal.WeekFields
17 | import java.util.*
18 | import javax.inject.Inject
19 |
20 | @HiltViewModel
21 | class CalendarYearVM @Inject constructor(private val navigator: Navigator) : ViewModel() {
22 | private lateinit var year: JetYear
23 | private lateinit var selectedDate: JetDay
24 | val firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
25 |
26 | var yearState = MutableStateFlow(null)
27 | private set
28 | var gridListSwitch = MutableStateFlow(true)
29 | private set
30 |
31 | init {
32 | setYear(LocalDate.now())
33 | }
34 |
35 | fun setYear(date: LocalDate) {
36 | viewModelScope.launch {
37 | selectedDate = JetDay(date, isPartOfMonth = true)
38 | withContext(Dispatchers.Default) {
39 | year = JetYear.current(selectedDate.date, firstDayOfWeek)
40 | }
41 | yearState.value = year
42 | }
43 | }
44 |
45 | fun navigateMonth(jetDay: JetDay) {
46 | navigator.navigate(Screen.CalendarMonthRoute.createRoute(jetDay.date.toEpochDay()))
47 | }
48 |
49 | fun switchView() {
50 | gridListSwitch.value = !gridListSwitch.value
51 | }
52 |
53 | fun nextYear() {
54 | yearState.value?.endDate?.plusDays(1)?.let { setYear(it) }
55 |
56 | }
57 |
58 | fun previousYear() {
59 | yearState.value?.startDate?.minusDays(1)?.let { setYear(it) }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/featcalendarview/src/main/java/com/praxis/feat/calendarview/ui/CalendarMonthVM.kt:
--------------------------------------------------------------------------------
1 | package com.praxis.feat.calendarview.ui
2 |
3 | import androidx.lifecycle.SavedStateHandle
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.mutualmobile.praxis.navigator.Navigator
7 | import com.mutualmobile.praxis.navigator.Screen
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import dev.baseio.libjetcalendar.data.JetMonth
10 | import kotlinx.coroutines.Dispatchers
11 | import kotlinx.coroutines.flow.MutableStateFlow
12 | import kotlinx.coroutines.launch
13 | import kotlinx.coroutines.withContext
14 | import java.time.DayOfWeek
15 | import java.time.LocalDate
16 | import java.time.temporal.WeekFields
17 | import java.util.*
18 | import javax.inject.Inject
19 |
20 | @HiltViewModel
21 | class CalendarMonthVM @Inject constructor(
22 | savedStateHandle: SavedStateHandle,
23 | private val navigator: Navigator
24 | ) : ViewModel() {
25 |
26 | private val monthDate =
27 | savedStateHandle.get(Screen.CalendarMonthRoute.navArguments.first().name)!!
28 | var selectedDate: LocalDate = LocalDate.ofEpochDay(monthDate)
29 | private val firstDayOfWeek: DayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek
30 |
31 | var titleState = MutableStateFlow("")
32 | private set
33 | var month = MutableStateFlow(null)
34 | private set
35 |
36 | init {
37 | dateSelected(selectedDate)
38 | }
39 |
40 | fun dateSelected(date: LocalDate) {
41 | viewModelScope.launch {
42 | selectedDate = date
43 | titleState.value = date.year.toString()
44 | val monthCurrent = withContext(Dispatchers.Default) {
45 | JetMonth.current(date, firstDayOfWeek)
46 | }
47 | month.value = monthCurrent
48 | }
49 | }
50 |
51 | fun navBack() {
52 | navigator.navigateUp()
53 | }
54 |
55 | fun nextMonth() {
56 | month.value?.endDate?.plusDays(1)?.let { dateSelected(it) }
57 | }
58 |
59 | fun previousMonth() {
60 | month.value?.startDate?.minusDays(1)?.let { dateSelected(it) }
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/app/proguard-specific.txt:
--------------------------------------------------------------------------------
1 | #MM Proguard Settings pertaining to this project
2 | # this is an extension to the recommended settings for android
3 | # provide in proguard-android.pro
4 | #
5 | # It is also an extention to the proguard configuration of Praxis
6 | #
7 | # Add proguard directives to this file if this project requires additional
8 | # configuration
9 |
10 | -keepnames !abstract class com.customername.android.injection.*
11 |
12 | #Keeping the members of that have static vars
13 | -keepclassmembers public class com.customername.android.** {
14 | public static * ;
15 | public *;
16 | }
17 |
18 | # Below will be classes you want to explicity keep AND obfuscate - you shouldn't need to do this unless your class is only referenced at runtime and not compile time (IE injected via annotation or reflection)
19 | #-keep,allowobfuscation class com.customername.android.** { *; }
20 |
21 | #Things you don't want to obfuscate and you don't want to be shrunk usually GSON pojos. Add your domain/JSON below here
22 | -keep class com.customername.android.model.** { *; }
23 |
24 | -dontwarn okio.**
25 | -dontwarn org.simpleframework.**
26 | -keep class com.google.common.** { *; }
27 |
28 |
29 | #Rxjava
30 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
31 | long producerIndex;
32 | long consumerIndex;
33 | }
34 |
35 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
36 | rx.internal.util.atomic.LinkedQueueNode producerNode;
37 | }
38 |
39 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
40 | rx.internal.util.atomic.LinkedQueueNode consumerNode;
41 | }
42 |
43 |
44 | # Retrofit2
45 | -dontwarn retrofit2.**
46 | -keep class retrofit2.** { *; }
47 | -keepattributes Signature
48 | -keepattributes Exceptions
49 | -keepattributes Annotation
50 |
51 | -dontwarn android.databinding.**
52 | -keep class android.databinding.** { *; }
53 | -dontwarn com.google.errorprone.annotations.**
54 |
55 |
56 | -keep class okhttp3.** { *; }
57 | -keep interface okhttp3.** { *; }
58 | -dontwarn okhttp3.**
59 | -dontwarn okio.**
60 | -dontwarn javax.annotation**
61 |
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/data/JetWeek.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.data
2 |
3 | import java.time.DayOfWeek
4 | import java.time.LocalDate
5 | import java.time.format.TextStyle
6 | import java.time.temporal.TemporalAdjusters
7 | import java.util.*
8 |
9 |
10 | fun dayNames(dayOfWeek: DayOfWeek): List {
11 | val days = mutableListOf()
12 | days.add(dayOfWeek)
13 | while (days.size != 7) {
14 | days.add(days.last().plus(1))
15 | }
16 | return days.map {
17 | it.getDisplayName(
18 | TextStyle.NARROW,
19 | Locale.getDefault()
20 | )
21 | }
22 | }
23 |
24 | class JetWeek private constructor(
25 | val startDate: LocalDate,
26 | val endDate: LocalDate,
27 | val monthOfWeek: Int,
28 | val dayOfWeek: DayOfWeek,
29 | ) : JetCalendarType() {
30 | lateinit var days: List
31 |
32 | companion object {
33 | fun current(
34 | date: LocalDate = LocalDate.now(),
35 | dayOfWeek: DayOfWeek
36 | ): JetWeek {
37 | val startOfCurrentWeek: LocalDate =
38 | date.with(TemporalAdjusters.previousOrSame(dayOfWeek))
39 | val lastDayOfWeek = dayOfWeek.plus(6) // or minus(1)
40 | val endOfWeek: LocalDate = date.with(TemporalAdjusters.nextOrSame(lastDayOfWeek))
41 | val week = JetWeek(startOfCurrentWeek, endOfWeek, date.monthValue, dayOfWeek)
42 | week.days = week.dates()
43 | return week
44 | }
45 | }
46 |
47 | fun dates(): List {
48 | val days = mutableListOf()
49 | val isPart = startDate.monthValue == this.monthOfWeek
50 | days.add(startDate.toJetDay(isPart))
51 | while (days.size != 7) {
52 | days.add(days.last().nextDay(this))
53 | }
54 | return days
55 | }
56 |
57 | }
58 |
59 |
60 | fun LocalDate.toJetDay(isPart: Boolean): JetDay {
61 | return JetDay(this, isPart)
62 | }
63 |
64 | private fun JetDay.nextDay(jetWeek: JetWeek): JetDay {
65 | val date = this.date.plusDays(1)
66 | val isPartOfMonth = this.date.plusDays(1).monthValue == jetWeek.monthOfWeek
67 | return JetDay(date, isPartOfMonth)
68 | }
69 |
70 | fun JetWeek.nextWeek(): JetWeek {
71 | val firstDay = this.endDate.plusDays(1)
72 | val week = JetWeek.current(firstDay, dayOfWeek = dayOfWeek)
73 | week.days = week.dates()
74 | return week
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/weekly/JetCalendarWeekView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.weekly
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyRow
7 | import androidx.compose.foundation.lazy.items
8 | import androidx.compose.foundation.shape.CircleShape
9 | import androidx.compose.material.MaterialTheme
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.*
12 | import androidx.compose.ui.Alignment
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.draw.clip
15 | import androidx.compose.ui.graphics.Color
16 | import androidx.compose.ui.text.TextStyle
17 | import androidx.compose.ui.text.font.FontWeight
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.unit.sp
20 | import dev.baseio.libjetcalendar.data.JetDay
21 | import dev.baseio.libjetcalendar.data.JetWeek
22 |
23 | @Composable
24 | fun JetCalendarWeekView(
25 | modifier: Modifier,
26 | week: JetWeek,
27 | onDateSelected: (JetDay) -> Unit,
28 | selectedDates: Set,
29 | isGridView: Boolean,
30 | ) {
31 | LazyRow(
32 | modifier = modifier
33 | .fillMaxWidth()
34 | .padding(bottom = 8.dp),
35 | verticalAlignment = Alignment.CenterVertically,
36 | horizontalArrangement = Arrangement.SpaceBetween,
37 | ) {
38 | items(week.days!!) { date ->
39 | Box(
40 | modifier = Modifier
41 | .size(if (!isGridView) 40.dp else 16.dp)
42 | .clip(CircleShape)
43 | .clickable {
44 | if (date.isPartOfMonth) {
45 | onDateSelected(date)
46 | }
47 | }
48 | .background(bgColor(selectedDates, date)),
49 | contentAlignment = Alignment.Center
50 | ) {
51 | Text(
52 | text = date.date.dayOfMonth.toString(),
53 | style = TextStyle(
54 | fontSize = if (isGridView) 8.sp else 14.sp,
55 | fontWeight = FontWeight.SemiBold,
56 | color = if (date.isPartOfMonth) MaterialTheme.typography.body1.color else Color.Transparent
57 | )
58 | )
59 | }
60 | }
61 | }
62 | }
63 |
64 | @Composable
65 | private fun bgColor(
66 | selectedDates: Set,
67 | date: JetDay
68 | ) = if (selectedDates.contains(date)) Color.Red else Color.Transparent
--------------------------------------------------------------------------------
/featcalendarview/src/main/java/com/praxis/feat/calendarview/ui/CalendarYearView.kt:
--------------------------------------------------------------------------------
1 | package com.praxis.feat.calendarview.ui
2 |
3 | import androidx.compose.foundation.layout.padding
4 | import androidx.compose.material.*
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.*
7 | import androidx.compose.runtime.*
8 | import androidx.compose.ui.Modifier
9 | import androidx.compose.ui.graphics.Color
10 | import androidx.compose.ui.unit.dp
11 | import androidx.hilt.navigation.compose.hiltViewModel
12 | import com.google.accompanist.insets.navigationBarsPadding
13 | import com.google.accompanist.insets.statusBarsPadding
14 | import com.mutualmobile.praxis.commonui.material.CommonTopAppBar
15 | import com.mutualmobile.praxis.commonui.theme.PraxisTheme
16 | import dev.baseio.libjetcalendar.data.JetDay
17 | import dev.baseio.libjetcalendar.yearly.JetCalendarYearlyView
18 | import java.time.LocalDate
19 |
20 | @Composable
21 | fun CalendarYearView(viewModel: CalendarYearVM = hiltViewModel()) {
22 | val viewType by viewModel.gridListSwitch.collectAsState()
23 |
24 | Scaffold(
25 | backgroundColor = PraxisTheme.colors.uiBackground,
26 | contentColor = PraxisTheme.colors.textSecondary,
27 | modifier = Modifier
28 | .statusBarsPadding()
29 | .navigationBarsPadding(),
30 | topBar = {
31 | CommonTopAppBar(title = {
32 | }, actions = {
33 | TextButton(modifier = Modifier.then(Modifier.padding(8.dp)),
34 | onClick = {
35 | viewModel.switchView()
36 | }, content = {
37 | Text(text = if (viewType) "Grid" else "List")
38 | })
39 | })
40 | }) {
41 | val yearState by viewModel.yearState.collectAsState()
42 | yearState?.let { jetYear ->
43 | JetCalendarYearlyView(
44 | onDateSelected = {
45 | updateViewForViewType(viewModel, it)
46 | },
47 | selectedDates = setOf(JetDay(LocalDate.now(), isPartOfMonth = true)),
48 | jetYear = jetYear,
49 | dayOfWeek = viewModel.firstDayOfWeek,
50 | isGridView = viewType,
51 | onNextYear = {
52 | viewModel.nextYear()
53 | },
54 | onPreviousYear = {
55 | viewModel.previousYear()
56 | },
57 | needsYearNavigator = true
58 | )
59 | }
60 |
61 | }
62 |
63 |
64 | }
65 |
66 | private fun updateViewForViewType(
67 | calendarYearVM: CalendarYearVM,
68 | jetDay: JetDay
69 | ) {
70 | calendarYearVM.navigateMonth(jetDay)
71 | }
72 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/theme/PraxisSurface.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.theme
2 |
3 | import androidx.compose.foundation.BorderStroke
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.border
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.material.LocalContentColor
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.CompositionLocalProvider
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.draw.clip
12 | import androidx.compose.ui.draw.shadow
13 | import androidx.compose.ui.graphics.Color
14 | import androidx.compose.ui.graphics.RectangleShape
15 | import androidx.compose.ui.graphics.Shape
16 | import androidx.compose.ui.graphics.compositeOver
17 | import androidx.compose.ui.unit.Dp
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.zIndex
20 | import kotlin.math.ln
21 |
22 | /**
23 | * An alternative to [androidx.compose.material.Surface]
24 | */
25 | @Composable
26 | fun PraxisSurface(
27 | modifier: Modifier = Modifier,
28 | shape: Shape = RectangleShape,
29 | color: Color = PraxisTheme.colors.uiBackground,
30 | contentColor: Color = PraxisTheme.colors.textSecondary,
31 | border: BorderStroke? = null,
32 | elevation: Dp = 0.dp,
33 | content: @Composable () -> Unit
34 | ) {
35 | Box(
36 | modifier = modifier
37 | .shadow(elevation = elevation, shape = shape, clip = false)
38 | .zIndex(elevation.value)
39 | .then(if (border != null) Modifier.border(border, shape) else Modifier)
40 | .background(
41 | color = getBackgroundColorForElevation(color, elevation),
42 | shape = shape
43 | )
44 | .clip(shape)
45 | ) {
46 | CompositionLocalProvider(LocalContentColor provides contentColor, content = content)
47 | }
48 | }
49 |
50 | @Composable
51 | private fun getBackgroundColorForElevation(
52 | color: Color,
53 | elevation: Dp
54 | ): Color {
55 | return if (elevation > 0.dp
56 | ) {
57 | color.withElevation(elevation)
58 | } else {
59 | color
60 | }
61 | }
62 |
63 | /**
64 | * Applies a [Color.White] overlay to this color based on the [elevation]. This increases visibility
65 | * of elevation for surfaces in a dark theme.
66 | */
67 | private fun Color.withElevation(elevation: Dp): Color {
68 | val foreground = calculateForeground(elevation)
69 | return foreground.compositeOver(this)
70 | }
71 |
72 | /**
73 | * @return the alpha-modified [Color.White] to overlay on top of the surface color to produce
74 | * the resultant color.
75 | */
76 | private fun calculateForeground(elevation: Dp): Color {
77 | val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 20f
78 | return Color.White.copy(alpha = alpha)
79 | }
--------------------------------------------------------------------------------
/featcalendarview/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(BuildPlugins.ANDROID_LIBRARY_PLUGIN)
3 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
4 | id(BuildPlugins.KOTLIN_KAPT)
5 | id(BuildPlugins.DAGGER_HILT)
6 | id(BuildPlugins.KOTLIN_PARCELABLE_PLUGIN)
7 | id("org.jlleitschuh.gradle.ktlint")
8 | }
9 |
10 | android {
11 | compileSdk = ProjectProperties.COMPILE_SDK
12 |
13 | defaultConfig {
14 | minSdk = (ProjectProperties.MIN_SDK)
15 | targetSdk = (ProjectProperties.TARGET_SDK)
16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | getByName("release") {
21 | isMinifyEnabled = false
22 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
23 | }
24 | }
25 |
26 |
27 | buildFeatures {
28 | compose = true
29 | }
30 |
31 | composeOptions {
32 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER
33 | }
34 | packagingOptions {
35 | resources.excludes.add("META-INF/LICENSE.txt")
36 | resources.excludes.add("META-INF/NOTICE.txt")
37 | resources.excludes.add("LICENSE.txt")
38 | resources.excludes.add( "/META-INF/{AL2.0,LGPL2.1}")
39 | }
40 |
41 | compileOptions {
42 | isCoreLibraryDesugaringEnabled = true
43 | sourceCompatibility = JavaVersion.VERSION_1_8
44 | targetCompatibility = JavaVersion.VERSION_1_8
45 | }
46 |
47 | kotlinOptions {
48 | jvmTarget = "1.8"
49 | }
50 |
51 | }
52 |
53 | // Required for annotation processing plugins like Dagger
54 | kapt {
55 | generateStubs = true
56 | correctErrorTypes = true
57 | }
58 |
59 | dependencies {
60 | /*Kotlin*/
61 | implementation(project(":data"))
62 | implementation(project(":domain"))
63 | implementation(project(":common"))
64 | implementation(project(":navigator"))
65 | implementation(project(":commonui"))
66 | implementation(project(":libjetcalendar"))
67 |
68 | coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
69 |
70 | api(Lib.Android.COMPOSE_UI)
71 | api(Lib.Android.COIL_COMPOSE)
72 | api(Lib.Android.COMPOSE_MATERIAL)
73 | api(Lib.Android.COMPOSE_UI)
74 | api(Lib.Android.COMPOSE_TOOLING)
75 | debugApi(Lib.Android.DEBUG_TOOLING)
76 | api(Lib.Android.ACT_COMPOSE)
77 |
78 | api(Lib.Android.appCompat)
79 | api(Lib.Kotlin.KTX_CORE)
80 |
81 | api(Lib.Android.ACCOMPANIST_INSETS)
82 |
83 | /*DI*/
84 | api(Lib.Di.hilt)
85 | api(Lib.Di.hiltNavigationCompose)
86 | api(Lib.Di.viewmodel)
87 |
88 | kapt(Lib.Di.hiltCompiler)
89 | kapt(Lib.Di.hiltAndroidCompiler)
90 |
91 | /* Logger */
92 | api(Lib.Logger.TIMBER)
93 | /* Async */
94 | api(Lib.Async.COROUTINES)
95 | api(Lib.Async.COROUTINES_ANDROID)
96 |
97 | testImplementation(TestLib.JUNIT)
98 | testImplementation(TestLib.CORE_TEST)
99 | testImplementation(TestLib.ANDROID_JUNIT)
100 | testImplementation(TestLib.ARCH_CORE)
101 | testImplementation(TestLib.MOCK_WEB_SERVER)
102 | testImplementation(TestLib.ROBO_ELECTRIC)
103 | testImplementation(TestLib.COROUTINES)
104 | testImplementation(TestLib.MOCKK)
105 | }
--------------------------------------------------------------------------------
/navigator/src/main/java/com/mutualmobile/praxis/navigator/PraxisNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.navigator
2 |
3 | import androidx.navigation.NavController
4 | import androidx.navigation.NavOptionsBuilder
5 | import androidx.navigation.navOptions
6 | import kotlinx.coroutines.flow.*
7 |
8 | class PraxisNavigator : Navigator() {
9 |
10 | override suspend fun handleNavigationCommands(navController: NavController) {
11 | navigationCommands
12 | .onSubscription { this@PraxisNavigator.navControllerFlow.value = navController }
13 | .onCompletion { this@PraxisNavigator.navControllerFlow.value = null }
14 | .collect { navController.handleNavigationCommand(it) }
15 | }
16 |
17 | override fun navigate(route: String, optionsBuilder: (NavOptionsBuilder.() -> Unit)?) {
18 | val options = optionsBuilder?.let { navOptions(it) }
19 | navigationCommands.tryEmit(NavigationCommand.NavigateToRoute(route, options))
20 | }
21 |
22 | override fun navigateAndClearBackStack(route: String) {
23 | navigationCommands.tryEmit(NavigationCommand.NavigateToRoute(route, navOptions {
24 | popUpTo(0)
25 | }))
26 | }
27 |
28 | override fun navigateUp() {
29 | navigationCommands.tryEmit(NavigationCommand.NavigateUp)
30 | }
31 |
32 | override fun popUpTo(route: String, inclusive: Boolean) {
33 | navigationCommands.tryEmit(NavigationCommand.PopUpToRoute(route, inclusive))
34 | }
35 |
36 | override fun navigateBackWithResult(
37 | key: String,
38 | result: T,
39 | destination: String?
40 | ) {
41 | navigationCommands.tryEmit(
42 | NavigationCommand.NavigateUpWithResult(
43 | key = key,
44 | result = result,
45 | destination = destination
46 | )
47 | )
48 | }
49 |
50 | override fun observeResult(key: String, route: String?): Flow {
51 | return navControllerFlow
52 | .filterNotNull()
53 | .flatMapLatest { navController ->
54 | val backStackEntry = route?.let { navController.getBackStackEntry(it) }
55 | ?: navController.currentBackStackEntry
56 |
57 | backStackEntry?.savedStateHandle?.let { savedStateHandle ->
58 | savedStateHandle.getLiveData(key)
59 | .asFlow()
60 | .filter { it != null }
61 | .onEach {
62 | // Nullify the result to avoid resubmitting it
63 | savedStateHandle.set(key, null)
64 | }
65 | } ?: emptyFlow()
66 | }
67 | }
68 |
69 | private fun NavController.handleNavigationCommand(navigationCommand: NavigationCommand) {
70 | when (navigationCommand) {
71 | is NavigationCommand.NavigateToRoute -> navigate(
72 | navigationCommand.route,
73 | navigationCommand.options
74 | )
75 | NavigationCommand.NavigateUp -> navigateUp()
76 | is NavigationCommand.PopUpToRoute -> popBackStack(
77 | navigationCommand.route,
78 | navigationCommand.inclusive
79 | )
80 | is NavigationCommand.NavigateUpWithResult<*> -> {
81 | val backStackEntry =
82 | navigationCommand.destination?.let { getBackStackEntry(it) }
83 | ?: previousBackStackEntry
84 | backStackEntry?.savedStateHandle?.set(
85 | navigationCommand.key,
86 | navigationCommand.result
87 | )
88 |
89 | navigationCommand.destination?.let {
90 | popBackStack(it, false)
91 | } ?: run {
92 | navigateUp()
93 | }
94 | }
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Manifest version information!
2 |
3 | plugins {
4 | id(BuildPlugins.ANDROID_APPLICATION_PLUGIN)
5 | id(BuildPlugins.KOTLIN_ANDROID_PLUGIN)
6 | id(BuildPlugins.KOTLIN_PARCELABLE_PLUGIN)
7 | id(BuildPlugins.KOTLIN_KAPT)
8 | id(BuildPlugins.DAGGER_HILT)
9 | id("org.jlleitschuh.gradle.ktlint")
10 | }
11 |
12 | subprojects {
13 | apply {
14 | from("variants.gradle.kts")
15 | }
16 | }
17 |
18 | // def preDexEnabled = "true" == System.getProperty("pre-dex", "true")
19 |
20 | android {
21 | compileSdk = (ProjectProperties.COMPILE_SDK)
22 |
23 | defaultConfig {
24 | applicationId = (ProjectProperties.APPLICATION_ID)
25 | minSdk = (ProjectProperties.MIN_SDK)
26 | targetSdk = (ProjectProperties.TARGET_SDK)
27 | versionCode = 1
28 | versionName = "1.0"
29 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
30 | vectorDrawables.useSupportLibrary = true
31 | }
32 |
33 |
34 |
35 | buildFeatures {
36 | compose = true
37 | }
38 |
39 | composeOptions {
40 | kotlinCompilerExtensionVersion = Lib.Android.COMPOSE_COMPILER
41 | }
42 | packagingOptions {
43 | resources.excludes.add("META-INF/LICENSE.txt")
44 | resources.excludes.add("META-INF/NOTICE.txt")
45 | resources.excludes.add("LICENSE.txt")
46 | resources.excludes.add( "/META-INF/{AL2.0,LGPL2.1}")
47 | }
48 |
49 | compileOptions {
50 | sourceCompatibility = JavaVersion.VERSION_1_8
51 | targetCompatibility = JavaVersion.VERSION_1_8
52 | }
53 |
54 | kotlinOptions {
55 | jvmTarget = "1.8"
56 | }
57 | }
58 |
59 | // Required for annotation processing plugins like Dagger
60 | kapt {
61 | generateStubs = true
62 | correctErrorTypes = true
63 | }
64 |
65 | dependencies {
66 | implementation(project(":featcalendarview"))
67 | implementation(project(":navigator"))
68 | implementation(project(":data"))
69 | implementation(project(":domain"))
70 | implementation(project(":common"))
71 | implementation(project(":commonui"))
72 |
73 | /* Android Designing and layout */
74 | implementation(Lib.Android.livedata)
75 | implementation(Lib.Android.navigationCompose)
76 | implementation(Lib.Kotlin.KT_STD)
77 | implementation(Lib.Android.MATERIAL_DESIGN)
78 | implementation(Lib.Android.CONSTRAINT_LAYOUT_COMPOSE)
79 | implementation(Lib.Android.ACCOMPANIST_INSETS)
80 |
81 | implementation(Lib.Android.appCompat)
82 | implementation(Lib.Kotlin.KTX_CORE)
83 |
84 | /*DI*/
85 | implementation(Lib.Di.hilt)
86 | implementation(Lib.Di.hiltNavigationCompose)
87 | implementation(Lib.Di.viewmodel)
88 |
89 | kapt(Lib.Di.hiltCompiler)
90 | kapt(Lib.Di.hiltAndroidCompiler)
91 |
92 | /* Logger */
93 | implementation(Lib.Logger.TIMBER)
94 | /* Async */
95 | implementation(Lib.Async.COROUTINES)
96 | implementation(Lib.Async.COROUTINES_ANDROID)
97 |
98 | /*Testing*/
99 | testImplementation(TestLib.JUNIT)
100 | testImplementation(TestLib.CORE_TEST)
101 | testImplementation(TestLib.ANDROID_JUNIT)
102 | testImplementation(TestLib.ARCH_CORE)
103 | testImplementation(TestLib.MOCK_WEB_SERVER)
104 | testImplementation(TestLib.ROBO_ELECTRIC)
105 | testImplementation(TestLib.COROUTINES)
106 | testImplementation(TestLib.MOCKK)
107 |
108 | androidTestImplementation("androidx.compose.ui:ui-test-junit4:${Lib.Android.COMPOSE_VERSION}")
109 | debugImplementation("androidx.compose.ui:ui-test-manifest:${Lib.Android.COMPOSE_VERSION}")
110 | }
111 |
--------------------------------------------------------------------------------
/featcalendarview/src/main/java/com/praxis/feat/calendarview/ui/CalendarMonthlyView.kt:
--------------------------------------------------------------------------------
1 | package com.praxis.feat.calendarview.ui
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.material.*
6 | import androidx.compose.material.icons.Icons
7 | import androidx.compose.material.icons.filled.Add
8 | import androidx.compose.material.icons.filled.ArrowBack
9 | import androidx.compose.material.icons.filled.Search
10 | import androidx.compose.runtime.*
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.unit.dp
14 | import androidx.hilt.navigation.compose.hiltViewModel
15 | import com.google.accompanist.insets.navigationBarsPadding
16 | import com.google.accompanist.insets.statusBarsPadding
17 | import com.mutualmobile.praxis.commonui.material.CommonTopAppBar
18 | import com.mutualmobile.praxis.commonui.theme.PraxisTheme
19 | import dev.baseio.libjetcalendar.data.JetDay
20 | import dev.baseio.libjetcalendar.data.toJetDay
21 | import dev.baseio.libjetcalendar.monthly.JetCalendarMonthlyView
22 | import dev.baseio.libjetcalendar.yearly.JetCalendarYearlyView
23 | import java.time.LocalDate
24 |
25 | @Composable
26 | fun CalendarMonthlyView(viewModel: CalendarMonthVM = hiltViewModel()) {
27 | Scaffold(
28 | backgroundColor = PraxisTheme.colors.uiBackground,
29 | contentColor = PraxisTheme.colors.textSecondary,
30 | modifier = Modifier
31 | .statusBarsPadding()
32 | .navigationBarsPadding(),
33 | topBar = {
34 | CommonTopAppBar(title = {
35 | AppBarTitle(viewModel)
36 | },
37 | navigationIcon = {
38 | BackButton(viewModel)
39 | },
40 | actions = {
41 | SearchIcon()
42 | AddEvents()
43 | })
44 | }) {
45 | MainContent(viewModel)
46 | }
47 |
48 |
49 | }
50 |
51 | @Composable
52 | private fun AppBarTitle(viewModel: CalendarMonthVM) {
53 | val titleText by viewModel.titleState.collectAsState()
54 | Text(
55 | text = titleText,
56 | style = MaterialTheme.typography.h6.copy(color = Color.Red)
57 | )
58 | }
59 |
60 | @Composable
61 | private fun MainContent(viewModel: CalendarMonthVM) {
62 | val month by viewModel.month.collectAsState()
63 | month?.let { jetMonth ->
64 | JetCalendarMonthlyView(
65 | jetMonth = jetMonth,
66 | onDateSelected = {
67 | viewModel.dateSelected(it.date)
68 | },
69 | selectedDates = setOf(viewModel.selectedDate.toJetDay(true)),
70 | isGridView = false,
71 | onNextMonth = {
72 | viewModel.nextMonth()
73 | },
74 | onPreviousMonth = {
75 | viewModel.previousMonth()
76 | },
77 | needsMonthNavigator = true
78 | )
79 | }
80 | }
81 |
82 | @Composable
83 | private fun AddEvents() {
84 | IconButton(modifier = Modifier.then(Modifier.padding(8.dp)),
85 | onClick = {
86 | }, content = {
87 | Icon(
88 | Icons.Filled.Add,
89 | "add events",
90 | tint = Color.Red
91 | )
92 | })
93 | }
94 |
95 | @Composable
96 | private fun SearchIcon() {
97 | IconButton(modifier = Modifier.then(Modifier.padding(8.dp)),
98 | onClick = {
99 | }, content = {
100 | Icon(
101 | Icons.Filled.Search,
102 | "search",
103 | tint = Color.Red
104 | )
105 | })
106 | }
107 |
108 | @Composable
109 | private fun BackButton(viewModel: CalendarMonthVM) {
110 | Icon(
111 | imageVector = Icons.Filled.ArrowBack,
112 | contentDescription = "Back",
113 | modifier = Modifier
114 | .padding(16.dp)
115 | .clickable {
116 | // Implement back action here
117 | viewModel.navBack()
118 | },
119 | tint = Color.Red
120 | )
121 | }
122 |
--------------------------------------------------------------------------------
/app/proguard-common.txt:
--------------------------------------------------------------------------------
1 | #MM Proguard Settings for Praxis
2 | # this is an extension to the recommended settings for android
3 | # provided in the default android proguard configuration
4 | #
5 | # This file should only be edited if Praxis requires base configuration changes
6 | # please put project specific directives in proguard-specific.txt
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface class:
10 | #-keepclassmembers class com.example.android.fragment.dialog.WebPortalDialogFragment$SSOInterface {
11 | # public *;
12 | #}
13 |
14 | -target 1.6
15 | -optimizationpasses 5
16 |
17 | # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
18 | -keepclasseswithmembernames class * {
19 | native ;
20 | }
21 |
22 | -keep public class android.net.http.SslError
23 | -keep public class android.webkit.WebViewClient
24 |
25 | -dontwarn android.webkit.WebView
26 | -dontwarn android.net.http.SslError
27 | -dontwarn android.webkit.WebViewClient
28 |
29 | #This will print mappings - very useful for troubleshooting.
30 | -dump ./build/class_files.txt
31 | -printseeds ./build/seeds.txt
32 | -printusage ./build/unused.txt
33 | -printmapping ./build/mapping.txt
34 |
35 | #Some recommended settings for running with Android
36 | -keepattributes **
37 | -keep public class * extends android.app.Activity
38 | -keep public class * extends android.app.Application
39 | -keep public class * extends android.app.Service
40 | -keep public class * extends android.content.BroadcastReceiver
41 | -keep public class * extends android.content.ContentProvider
42 | -keep public class * extends android.app.Fragment
43 | -keep public class * extends android.support.v4.app.Fragment
44 |
45 | # There's no way to keep all @Observes methods, so use the On*Event convention to identify event handlers
46 | -keepclassmembers class * {
47 | void *(**On*Event);
48 | }
49 |
50 | #Need this to keep serializable members as is
51 | -keepclassmembers class * implements java.io.Serializable {
52 | static final long serialVersionUID;
53 | private static final java.io.ObjectStreamField[] serialPersistentFields;
54 | private void writeObject(java.io.ObjectOutputStream);
55 | private void readObject(java.io.ObjectInputStream);
56 | java.lang.Object writeReplace();
57 | java.lang.Object readResolve();
58 | }
59 |
60 | #Handling library optimization
61 | #-repackageclasses ''
62 | -allowaccessmodification
63 | # The -optimizations option disables some arithmetic simplifications that Dalvik 1.0 and 1.5 can't handle.
64 | -optimizations !code/simplification/arithmetic,!code/allocation/variable
65 |
66 | -keep public enum * {}
67 | -keep public interface * {}
68 | -keepattributes **
69 |
70 | # Keep db4o around
71 | -keep public class com.db4o.** { *; }
72 | -dontwarn sun.misc.Unsafe
73 |
74 | # Keep httpmime around
75 | -keep class org.apache.** { *; }
76 |
77 | # Keep gson around
78 | -keep class com.google.gson.stream.** { *; }
79 |
80 | # Keep hockey around
81 | -keep class net.hockeyapp.** { *; }
82 |
83 | # Keep system tests
84 | -keep class com.customername.androidui.tests.system.** { *; }
85 |
86 | #Avoid 3rd party library warnings
87 | -dontwarn net.hockeyapp.**
88 | -dontwarn javax.xml.stream.**
89 | -dontwarn java.awt.**,javax.security.**,java.beans.**
90 | -dontwarn org.apache.tools.ant.**
91 | -dontwarn org.simpleframework.**
92 | -dontwarn org.junit.**
93 | -dontwarn android.support.**
94 | -dontwarn javax.management.**
95 | -dontwarn java.lang.management.**
96 | -dontwarn android.test.**
97 | -dontwarn org.apache.commons.**
98 | -dontwarn com.google.gson.mm.internal.UnsafeAllocator.**
99 | -dontwarn com.google.inject.**
100 | -dontwarn org.mockito.**
101 | -dontwarn com.jayway.**
102 | -dontwarn org.objenesis.instantiator.**
103 |
104 | -keepattributes *Annotation*
105 |
106 | -keepclassmembers,allowobfuscation class * {
107 | @javax.inject.* *;
108 | @dagger.* *;
109 | ();
110 | }
111 |
112 | -keep class **$$ModuleAdapter
113 | -keep class **$$InjectAdapter
114 | -keep class **$$StaticInjection
115 |
116 | -keepnames class dagger.Lazy
117 | -keepnames class com.mutualmobile.**
118 |
119 | -keepclassmembers class ** {
120 | @com.squareup.otto.Subscribe public *;
121 | @com.squareup.otto.Produce public *;
122 | }
123 |
124 |
125 | -keep class com.google.common.** { *; }
126 | -dontwarn com.google.common.**
127 |
128 | -keepattributes *Annotation*,Signature
129 | -dontwarn retrofit.**
130 | -keep class retrofit.** { *; }
131 | -keepclasseswithmembers class * {
132 | @retrofit.* ;
133 | }
134 |
135 | -dontwarn java.lang.invoke.*
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/monthly/JetCalendarMonthlyView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.monthly
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.foundation.lazy.items
6 | import androidx.compose.material.Icon
7 | import androidx.compose.material.IconButton
8 | import androidx.compose.material.MaterialTheme
9 | import androidx.compose.material.Text
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.filled.ArrowBack
12 | import androidx.compose.material.icons.filled.ArrowForward
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.text.TextStyle
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.sp
21 | import dev.baseio.libjetcalendar.data.*
22 | import dev.baseio.libjetcalendar.weekly.JetCalendarWeekView
23 | import java.time.DayOfWeek
24 |
25 | @Composable
26 | fun JetCalendarMonthlyView(
27 | jetMonth: JetMonth,
28 | onDateSelected: (JetDay) -> Unit,
29 | selectedDates: Set,
30 | isGridView: Boolean,
31 | needsMonthNavigator: Boolean = false,
32 | onPreviousMonth: () -> Unit = {},
33 | onNextMonth: () -> Unit = {}
34 | ) {
35 | Column(
36 | modifier = Modifier
37 | .fillMaxWidth()
38 | .wrapContentHeight()
39 | .padding(4.dp),
40 | verticalArrangement = Arrangement.SpaceAround,
41 | ) {
42 | if (needsMonthNavigator) {
43 | MonthNavigator(onPreviousMonth, jetMonth, isGridView, selectedDates, onNextMonth)
44 | } else {
45 | MonthName(jetMonth, isGridView, selectedDates)
46 | }
47 | jetMonth.monthWeeks.forEach { week ->
48 | JetCalendarWeekView(
49 | modifier = Modifier.fillMaxWidth(),
50 | week = week,
51 | onDateSelected = onDateSelected,
52 | selectedDates = selectedDates,
53 | isGridView
54 | )
55 | }
56 |
57 | }
58 | }
59 |
60 | @Composable
61 | private fun MonthNavigator(
62 | onPreviousMonth: () -> Unit,
63 | jetMonth: JetMonth,
64 | isGridView: Boolean,
65 | selectedDates: Set,
66 | onNextMonth: () -> Unit
67 | ) {
68 | Row(
69 | horizontalArrangement = Arrangement.SpaceBetween,
70 | modifier = Modifier.fillMaxWidth(),
71 | verticalAlignment = Alignment.CenterVertically
72 | ) {
73 | IconButton(onClick = {
74 | onPreviousMonth()
75 | }) {
76 | Icon(
77 | Icons.Filled.ArrowBack, "Previous Month",
78 | tint = Color.Red
79 | )
80 | }
81 | MonthName(jetMonth, isGridView, selectedDates)
82 | IconButton(onClick = {
83 | onNextMonth()
84 | }) {
85 | Icon(
86 | Icons.Filled.ArrowForward, "Next Month",
87 | tint = Color.Red
88 | )
89 | }
90 | }
91 | }
92 |
93 | @Composable
94 | private fun MonthName(
95 | jetMonth: JetMonth,
96 | isGridView: Boolean,
97 | selectedDates: Set
98 | ) {
99 | Text(
100 | text = jetMonth.name(),
101 | style = TextStyle(
102 | fontSize = if (isGridView) 16.sp else 18.sp,
103 | fontWeight = FontWeight.Medium,
104 | color = colorCurrentMonthSelected(selectedDates, jetMonth)
105 | ),
106 | modifier = Modifier.padding(8.dp)
107 | )
108 | }
109 |
110 | @Composable
111 | fun colorCurrentMonthSelected(selectedDates: Set, jetMonth: JetMonth): Color {
112 | return if (isSameMonth(
113 | jetMonth,
114 | selectedDates
115 | )
116 | ) Color.Red else MaterialTheme.typography.body1.color
117 | }
118 |
119 | @Composable
120 | private fun isSameMonth(
121 | jetMonth: JetMonth,
122 | selectedDates: Set
123 | ): Boolean {
124 | return selectedDates.any { "${it.date.monthValue}${it.date.year}" == "${jetMonth.startDate.monthValue}${jetMonth.startDate.year}" }
125 | }
126 |
127 |
128 | @Composable
129 | fun WeekNames(isGridView: Boolean, dayOfWeek: DayOfWeek) {
130 | Row(
131 | modifier = Modifier
132 | .fillMaxWidth()
133 | .padding(start = 16.dp, end = 16.dp),
134 | verticalAlignment = Alignment.CenterVertically,
135 | horizontalArrangement = Arrangement.SpaceBetween
136 | ) {
137 | dayNames(dayOfWeek = dayOfWeek).forEach {
138 | Box(
139 | modifier = Modifier
140 | .padding(2.dp),
141 | contentAlignment = Alignment.Center
142 | ) {
143 | Text(
144 | text = it, modifier = Modifier.padding(2.dp),
145 | style = TextStyle(
146 | fontSize = if (isGridView) 8.sp else 12.sp,
147 | fontWeight = FontWeight.Bold
148 | )
149 | )
150 | }
151 |
152 | }
153 | }
154 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/theme/SystemUiController.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.theme
2 |
3 | import android.os.Build
4 | import android.view.View
5 | import android.view.Window
6 | import androidx.compose.runtime.staticCompositionLocalOf
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.graphics.compositeOver
9 | import androidx.compose.ui.graphics.luminance
10 | import androidx.compose.ui.graphics.toArgb
11 |
12 | interface SystemUiController {
13 | fun setStatusBarColor(
14 | color: Color,
15 | darkIcons: Boolean = color.luminance() > 0.5f,
16 | transformColorForLightContent: (Color) -> Color = BlackScrimmed
17 | )
18 |
19 | fun setNavigationBarColor(
20 | color: Color,
21 | darkIcons: Boolean = color.luminance() > 0.5f,
22 | transformColorForLightContent: (Color) -> Color = BlackScrimmed
23 | )
24 |
25 | fun setSystemBarsColor(
26 | color: Color,
27 | darkIcons: Boolean = color.luminance() > 0.5f,
28 | transformColorForLightContent: (Color) -> Color = BlackScrimmed
29 | )
30 | }
31 |
32 | fun SystemUiController(window: Window): SystemUiController {
33 | return SystemUiControllerImpl(window)
34 | }
35 |
36 | /**
37 | * A helper class for setting the navigation and status bar colors for a [Window], gracefully
38 | * degrading behavior based upon API level.
39 | */
40 | private class SystemUiControllerImpl(private val window: Window) : SystemUiController {
41 |
42 | /**
43 | * Set the status bar color.
44 | *
45 | * @param color The **desired** [Color] to set. This may require modification if running on an
46 | * API level that only supports white status bar icons.
47 | * @param darkIcons Whether dark status bar icons would be preferable. Only available on
48 | * API 23+.
49 | * @param transformColorForLightContent A lambda which will be invoked to transform [color] if
50 | * dark icons were requested but are not available. Defaults to applying a black scrim.
51 | */
52 | override fun setStatusBarColor(
53 | color: Color,
54 | darkIcons: Boolean,
55 | transformColorForLightContent: (Color) -> Color
56 | ) {
57 | val statusBarColor = when {
58 | darkIcons && Build.VERSION.SDK_INT < 23 -> transformColorForLightContent(color)
59 | else -> color
60 | }
61 | window.statusBarColor = statusBarColor.toArgb()
62 |
63 | if (Build.VERSION.SDK_INT >= 23) {
64 | @Suppress("DEPRECATION")
65 | if (darkIcons) {
66 | window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or
67 | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
68 | } else {
69 | window.decorView.systemUiVisibility = window.decorView.systemUiVisibility and
70 | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * Set the navigation bar color.
77 | *
78 | * @param color The **desired** [Color] to set. This may require modification if running on an
79 | * API level that only supports white navigation bar icons. Additionally this will be ignored
80 | * and [Color.Transparent] will be used on API 29+ where gesture navigation is preferred or the
81 | * system UI automatically applies background protection in other navigation modes.
82 | * @param darkIcons Whether dark navigation bar icons would be preferable. Only available on
83 | * API 26+.
84 | * @param transformColorForLightContent A lambda which will be invoked to transform [color] if
85 | * dark icons were requested but are not available. Defaults to applying a black scrim.
86 | */
87 | override fun setNavigationBarColor(
88 | color: Color,
89 | darkIcons: Boolean,
90 | transformColorForLightContent: (Color) -> Color
91 | ) {
92 | val navBarColor = when {
93 | Build.VERSION.SDK_INT >= 29 -> Color.Transparent // For gesture nav
94 | darkIcons && Build.VERSION.SDK_INT < 26 -> transformColorForLightContent(color)
95 | else -> color
96 | }
97 | window.navigationBarColor = navBarColor.toArgb()
98 |
99 | if (Build.VERSION.SDK_INT >= 26) {
100 | @Suppress("DEPRECATION")
101 | if (darkIcons) {
102 | window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or
103 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
104 | } else {
105 | window.decorView.systemUiVisibility = window.decorView.systemUiVisibility and
106 | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
107 | }
108 | }
109 | }
110 |
111 | /**
112 | * Set the status and navigation bars to [color].
113 | *
114 | * @see setStatusBarColor
115 | * @see setNavigationBarColor
116 | */
117 | override fun setSystemBarsColor(
118 | color: Color,
119 | darkIcons: Boolean,
120 | transformColorForLightContent: (Color) -> Color
121 | ) {
122 | setStatusBarColor(color, darkIcons, transformColorForLightContent)
123 | setNavigationBarColor(color, darkIcons, transformColorForLightContent)
124 | }
125 | }
126 |
127 | /**
128 | * An [androidx.compose.runtime.CompositionLocalProvider] holding the current [LocalSysUiController]. Defaults to a
129 | * no-op controller; consumers should [provide][androidx.compose.runtime.CompositionLocalProvider] a real one.
130 | */
131 | val LocalSysUiController = staticCompositionLocalOf {
132 | FakeSystemUiController
133 | }
134 |
135 | private val BlackScrim = Color(0f, 0f, 0f, 0.2f) // 20% opaque black
136 | private val BlackScrimmed: (Color) -> Color = { original ->
137 | BlackScrim.compositeOver(original)
138 | }
139 |
140 | /**
141 | * A fake implementation, useful as a default or used in Previews.
142 | */
143 | private object FakeSystemUiController : SystemUiController {
144 | override fun setStatusBarColor(
145 | color: Color,
146 | darkIcons: Boolean,
147 | transformColorForLightContent: (Color) -> Color
148 | ) = Unit
149 |
150 | override fun setNavigationBarColor(
151 | color: Color,
152 | darkIcons: Boolean,
153 | transformColorForLightContent: (Color) -> Color
154 | ) = Unit
155 |
156 | override fun setSystemBarsColor(
157 | color: Color,
158 | darkIcons: Boolean,
159 | transformColorForLightContent: (Color) -> Color
160 | ) = Unit
161 | }
162 |
--------------------------------------------------------------------------------
/libjetcalendar/src/main/java/dev/baseio/libjetcalendar/yearly/JetCalendarYearlyView.kt:
--------------------------------------------------------------------------------
1 | package dev.baseio.libjetcalendar.yearly
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.*
7 | import androidx.compose.material.CircularProgressIndicator
8 | import androidx.compose.material.Icon
9 | import androidx.compose.material.IconButton
10 | import androidx.compose.material.Text
11 | import androidx.compose.material.icons.Icons
12 | import androidx.compose.material.icons.filled.ArrowBack
13 | import androidx.compose.material.icons.filled.ArrowForward
14 | import androidx.compose.runtime.*
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.text.TextStyle
19 | import androidx.compose.ui.text.font.FontWeight
20 | import androidx.compose.ui.unit.dp
21 | import androidx.compose.ui.unit.sp
22 | import dev.baseio.libjetcalendar.data.*
23 | import dev.baseio.libjetcalendar.monthly.JetCalendarMonthlyView
24 | import dev.baseio.libjetcalendar.monthly.WeekNames
25 | import java.time.DayOfWeek
26 |
27 | @OptIn(ExperimentalFoundationApi::class)
28 | @Composable
29 | fun JetCalendarYearlyView(
30 | jetYear: JetYear,
31 | onDateSelected: (JetDay) -> Unit,
32 | selectedDates: Set,
33 | isGridView: Boolean = true,
34 | dayOfWeek: DayOfWeek,
35 | startIndex: Int = 0,
36 | needsYearNavigator: Boolean = false,
37 | onPreviousYear: () -> Unit = {},
38 | onNextYear: () -> Unit = {}
39 | ) {
40 | // affected by https://stackoverflow.com/questions/69739108/how-to-save-paging-state-of-lazycolumn-during-navigation-in-jetpack-compose
41 | val listState = rememberLazyListState(startIndex)
42 |
43 | YearViewInternal(
44 | listState,
45 | jetYear,
46 | onDateSelected,
47 | selectedDates,
48 | isGridView,
49 | dayOfWeek = dayOfWeek, needsYearNavigator, onPreviousYear, onNextYear
50 | )
51 | }
52 |
53 |
54 | @ExperimentalFoundationApi
55 | @Composable
56 | private fun YearViewInternal(
57 | listState: LazyListState,
58 | jetYear: JetYear,
59 | onDateSelected: (JetDay) -> Unit,
60 | selectedDates: Set,
61 | isGridView: Boolean,
62 | dayOfWeek: DayOfWeek,
63 | needsYearNavigator: Boolean,
64 | onPreviousYear: () -> Unit,
65 | onNextYear: () -> Unit,
66 | ) {
67 | when (jetYear.yearMonths.size) {
68 | 0 -> CircularProgressIndicator(color = Color.Black, modifier = Modifier.padding(8.dp))
69 | else -> {
70 | if (isGridView) {
71 | GridViewYearly(
72 | listState,
73 | jetYear.yearMonths,
74 | onDateSelected,
75 | selectedDates,
76 | isGridView,
77 | needsYearNavigator,
78 | onPreviousYear,
79 | onNextYear
80 | )
81 | } else {
82 | Column {
83 | YearNavigatorHeader(jetYear.year(), onPreviousYear, onNextYear)
84 | WeekNames(isGridView, dayOfWeek = dayOfWeek)
85 | ListViewYearly(
86 | listState, jetYear.yearMonths, onDateSelected, selectedDates, isGridView,
87 | needsYearNavigator,
88 | onPreviousYear,
89 | onNextYear
90 | )
91 | }
92 | }
93 | }
94 | }
95 |
96 | }
97 |
98 | @ExperimentalFoundationApi
99 | @Composable
100 | private fun ListViewYearly(
101 | listState: LazyListState,
102 | pagedMonths: List,
103 | onDateSelected: (JetDay) -> Unit,
104 | selectedDates: Set,
105 | isGridView: Boolean,
106 | needsYearNavigator: Boolean,
107 | onPreviousYear: () -> Unit,
108 | onNextYear: () -> Unit
109 | ) {
110 | LazyColumn(
111 | state = listState,
112 | modifier = Modifier
113 | .fillMaxWidth()
114 | .fillMaxHeight()
115 | ) {
116 | for (index in pagedMonths.indices) {
117 | item {
118 | CalendarMonthlyBox(
119 | pagedMonths,
120 | index,
121 | onDateSelected,
122 | selectedDates,
123 | isGridView
124 | )
125 | }
126 | }
127 | }
128 | }
129 |
130 | @ExperimentalFoundationApi
131 | @Composable
132 | private fun GridViewYearly(
133 | listState: LazyListState,
134 | pagedMonths: List,
135 | onDateSelected: (JetDay) -> Unit,
136 | selectedDates: Set,
137 | isGridView: Boolean,
138 | needsYearNavigator: Boolean,
139 | onPreviousYear: () -> Unit,
140 | onNextYear: () -> Unit,
141 | ) {
142 | LazyVerticalGrid(
143 | cells = GridCells.Fixed(3),
144 | state = listState,
145 | modifier = Modifier
146 | .fillMaxWidth()
147 | .fillMaxHeight()
148 | ) {
149 | for (index in pagedMonths.indices) {
150 | when {
151 | index % 12 == 0 -> {
152 | item(span = { GridItemSpan(3) }) {
153 | if (needsYearNavigator) {
154 | YearNavigatorHeader(pagedMonths[index].year(), onPreviousYear, onNextYear)
155 | } else {
156 | YearHeader(pagedMonths[index].year())
157 | }
158 | }
159 | item {
160 | CalendarMonthlyBox(
161 | pagedMonths,
162 | index,
163 | onDateSelected,
164 | selectedDates,
165 | isGridView
166 | )
167 | }
168 | }
169 | else -> {
170 | item {
171 | CalendarMonthlyBox(
172 | pagedMonths,
173 | index,
174 | onDateSelected,
175 | selectedDates,
176 | isGridView
177 | )
178 | }
179 | }
180 | }
181 | }
182 | }
183 | }
184 |
185 | @Composable
186 | fun YearNavigatorHeader(year: String, onPreviousYear: () -> Unit, onNextYear: () -> Unit) {
187 | Row(
188 | horizontalArrangement = Arrangement.SpaceBetween,
189 | modifier = Modifier.fillMaxWidth(),
190 | verticalAlignment = Alignment.CenterVertically
191 | ) {
192 | IconButton(onClick = {
193 | onPreviousYear()
194 | }) {
195 | Icon(
196 | Icons.Filled.ArrowBack, "Previous Year",
197 | tint = Color.Red
198 | )
199 | }
200 | YearHeader(year)
201 | IconButton(onClick = {
202 | onNextYear()
203 | }) {
204 | Icon(
205 | Icons.Filled.ArrowForward, "Next Year",
206 | tint = Color.Red
207 | )
208 | }
209 | }
210 | }
211 |
212 | @Composable
213 | private fun YearHeader(
214 | year: String
215 | ) {
216 | Text(
217 | text = year,
218 | modifier = Modifier.padding(8.dp),
219 | style = TextStyle(
220 | color = Color.Red,
221 | fontSize = 24.sp,
222 | fontWeight = FontWeight.SemiBold
223 | )
224 | )
225 | }
226 |
227 | @Composable
228 | private fun CalendarMonthlyBox(
229 | pagedMonths: List,
230 | index: Int,
231 | onDateSelected: (JetDay) -> Unit,
232 | selectedDates: Set,
233 | isGridView: Boolean,
234 | ) {
235 | JetCalendarMonthlyView(
236 | pagedMonths[index],
237 | {
238 | onDateSelected(it)
239 | },
240 | selectedDates, isGridView = isGridView
241 | )
242 | }
243 |
--------------------------------------------------------------------------------
/commonui/src/main/java/com/mutualmobile/praxis/commonui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.mutualmobile.praxis.commonui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.Colors
5 | import androidx.compose.material.MaterialTheme
6 | import androidx.compose.runtime.Composable
7 | import androidx.compose.runtime.CompositionLocalProvider
8 | import androidx.compose.runtime.SideEffect
9 | import androidx.compose.runtime.Stable
10 | import androidx.compose.runtime.getValue
11 | import androidx.compose.runtime.mutableStateOf
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.setValue
14 | import androidx.compose.runtime.staticCompositionLocalOf
15 | import androidx.compose.ui.graphics.Color
16 |
17 | private val LightColorPalette = PraxisColorPalette(
18 | brand = White,
19 | accent = PraxisColor,
20 | accentDark = PraxisColor,
21 | iconTint = Grey,
22 | uiBackground = Neutral0,
23 | uiBorder = VeryLightGrey,
24 | uiFloated = FunctionalGrey,
25 | textPrimary = TextPrimary,
26 | textSecondary = TextSecondary,
27 | textSecondaryDark = TextSecondaryDark,
28 | textHelp = Neutral6,
29 | textInteractive = Neutral0,
30 | textLink = Ocean11,
31 | iconSecondary = Neutral7,
32 | iconInteractive = PraxisColor,
33 | iconInteractiveInactive = Grey,
34 | error = FunctionalRed,
35 | progressIndicatorBg = LightGrey,
36 | switchColor = PraxisColor,
37 | statusBarColor = PraxisColor,
38 | isDark = false,
39 | searchBarBgColor = LightGrey,
40 | buttonColor = PraxisColor,
41 | buttonTextColor = White
42 | )
43 |
44 | private val DarkColorPalette = PraxisColorPalette(
45 | brand = Shadow1,
46 | accent = PraxisColor,
47 | accentDark = DarkGreen,
48 | iconTint = Shadow1,
49 | uiBackground = GreyBg,
50 | uiBorder = Neutral3,
51 | uiFloated = FunctionalDarkGrey,
52 | textPrimary = Shadow1,
53 | textSecondary = Neutral0,
54 | textHelp = Neutral1,
55 | textInteractive = Neutral7,
56 | textLink = Ocean2,
57 | iconPrimary = Neutral3,
58 | iconSecondary = Neutral0,
59 | textSecondaryDark = Neutral0,
60 | iconInteractive = White,
61 | iconInteractiveInactive = Neutral2,
62 | error = FunctionalRedDark,
63 | progressIndicatorBg = LightGrey,
64 | switchColor = PraxisColor,
65 | statusBarColor = GreyBg,
66 | isDark = true,
67 | searchBarBgColor = SearchBarDarkColor,
68 | buttonColor = PraxisColor,
69 | buttonTextColor = White
70 |
71 | )
72 |
73 | @Composable
74 | fun PraxisTheme(
75 | darkTheme: Boolean = isSystemInDarkTheme(),
76 | content: @Composable () -> Unit
77 | ) {
78 | val colors = if (darkTheme) DarkColorPalette else LightColorPalette
79 |
80 | val sysUiController = LocalSysUiController.current
81 | SideEffect {
82 | sysUiController.setSystemBarsColor(
83 | color = colors.uiBackground.copy(alpha = AlphaNearOpaque)
84 | )
85 | }
86 |
87 | ProvidePraxisColors(colors) {
88 | MaterialTheme(
89 | colors = debugColors(darkTheme),
90 | typography = PraxisTypography,
91 | shapes = PraxisShapes,
92 | content = content
93 | )
94 | }
95 | }
96 |
97 | object PraxisTheme {
98 | val colors: PraxisColorPalette
99 | @Composable
100 | get() = LocalPraxisColor.current
101 | }
102 |
103 | /**
104 | * Praxis custom Color Palette
105 | */
106 | @Stable
107 | class PraxisColorPalette(
108 | brand: Color,
109 | accent: Color,
110 | accentDark: Color,
111 | iconTint: Color,
112 | uiBackground: Color,
113 | uiBorder: Color,
114 | uiFloated: Color,
115 | textPrimary: Color = brand,
116 | textSecondaryDark: Color,
117 | textSecondary: Color,
118 | textHelp: Color,
119 | textInteractive: Color,
120 | textLink: Color,
121 | iconPrimary: Color = brand,
122 | iconSecondary: Color,
123 | iconInteractive: Color,
124 | iconInteractiveInactive: Color,
125 | error: Color,
126 | notificationBadge: Color = error,
127 | progressIndicatorBg: Color,
128 | switchColor: Color,
129 | statusBarColor: Color,
130 | isDark: Boolean,
131 | searchBarBgColor: Color,
132 | buttonColor: Color,
133 | buttonTextColor: Color
134 | ) {
135 | var searchBarBg by mutableStateOf(searchBarBgColor)
136 | private set
137 | var brand by mutableStateOf(brand)
138 | private set
139 | var accent by mutableStateOf(accent)
140 | private set
141 | var accentDark by mutableStateOf(accentDark)
142 | private set
143 | var iconTint by mutableStateOf(iconTint)
144 | private set
145 | var uiBackground by mutableStateOf(uiBackground)
146 | private set
147 | var statusBarColor by mutableStateOf(statusBarColor)
148 | private set
149 | var uiBorder by mutableStateOf(uiBorder)
150 | private set
151 | var uiFloated by mutableStateOf(uiFloated)
152 | private set
153 | var textPrimary by mutableStateOf(textPrimary)
154 | private set
155 | var textSecondary by mutableStateOf(textSecondary)
156 | private set
157 | var textSecondaryDark by mutableStateOf(textSecondaryDark)
158 | private set
159 | var textHelp by mutableStateOf(textHelp)
160 | private set
161 | var textInteractive by mutableStateOf(textInteractive)
162 | private set
163 | var textLink by mutableStateOf(textLink)
164 | private set
165 | var iconPrimary by mutableStateOf(iconPrimary)
166 | private set
167 | var iconSecondary by mutableStateOf(iconSecondary)
168 | private set
169 | var iconInteractive by mutableStateOf(iconInteractive)
170 | private set
171 | var iconInteractiveInactive by mutableStateOf(iconInteractiveInactive)
172 | private set
173 | var error by mutableStateOf(error)
174 | private set
175 | var notificationBadge by mutableStateOf(notificationBadge)
176 | private set
177 | var progressIndicatorBg by mutableStateOf(progressIndicatorBg)
178 | private set
179 | var switchColor by mutableStateOf(switchColor)
180 | private set
181 | var isDark by mutableStateOf(isDark)
182 | private set
183 | var buttonColor by mutableStateOf(buttonColor)
184 | private set
185 |
186 | var buttonTextColor by mutableStateOf(buttonTextColor)
187 | private set
188 |
189 |
190 | fun update(other: PraxisColorPalette) {
191 | brand = other.brand
192 | uiBackground = other.uiBackground
193 | uiBorder = other.uiBorder
194 | uiFloated = other.uiFloated
195 | textPrimary = other.textPrimary
196 | textSecondary = other.textSecondary
197 | textHelp = other.textHelp
198 | textInteractive = other.textInteractive
199 | textLink = other.textLink
200 | iconPrimary = other.iconPrimary
201 | iconSecondary = other.iconSecondary
202 | iconInteractive = other.iconInteractive
203 | iconInteractiveInactive = other.iconInteractiveInactive
204 | error = other.error
205 | notificationBadge = other.notificationBadge
206 | switchColor = other.switchColor
207 | statusBarColor = other.statusBarColor
208 | isDark = other.isDark
209 | searchBarBg = other.searchBarBg
210 | buttonColor = other.buttonColor
211 | buttonTextColor = other.buttonTextColor
212 | }
213 | }
214 |
215 | @Composable
216 | fun ProvidePraxisColors(
217 | colors: PraxisColorPalette,
218 | content: @Composable () -> Unit
219 | ) {
220 | val colorPalette = remember { colors }
221 | colorPalette.update(colors)
222 | CompositionLocalProvider(LocalPraxisColor provides colorPalette, content = content)
223 | }
224 |
225 | private val LocalPraxisColor = staticCompositionLocalOf {
226 | error("No PraxisColorPalette provided")
227 | }
228 |
229 | /**
230 | * A Material [Colors] implementation which sets all colors to [debugColor] to discourage usage of
231 | * [MaterialTheme.colors] in preference to [PraxisTheme.colors].
232 | */
233 | fun debugColors(
234 | darkTheme: Boolean,
235 | debugColor: Color = GreyBg
236 | ) = Colors(
237 | primary = debugColor,
238 | primaryVariant = debugColor,
239 | secondary = debugColor,
240 | secondaryVariant = debugColor,
241 | background = debugColor,
242 | surface = debugColor,
243 | error = debugColor,
244 | onPrimary = debugColor,
245 | onSecondary = debugColor,
246 | onBackground = debugColor,
247 | onSurface = debugColor,
248 | onError = debugColor,
249 | isLight = !darkTheme
250 | )
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------