├── ios
├── Tuist.swift
├── App
│ ├── Resources
│ │ └── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ ├── app_icon.png
│ │ │ └── Contents.json
│ ├── Configs
│ │ ├── Target.xcconfig
│ │ └── Project.xcconfig
│ └── Sources
│ │ └── NewsfeedApp.swift
├── CommonDesign
│ ├── Resources
│ │ └── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── black.colorset
│ │ │ └── Contents.json
│ │ │ ├── black_2.colorset
│ │ │ └── Contents.json
│ │ │ ├── black_3.colorset
│ │ │ └── Contents.json
│ │ │ ├── blue.colorset
│ │ │ └── Contents.json
│ │ │ ├── blue_2.colorset
│ │ │ └── Contents.json
│ │ │ ├── blue_3.colorset
│ │ │ └── Contents.json
│ │ │ ├── blue_4.colorset
│ │ │ └── Contents.json
│ │ │ ├── blue_5.colorset
│ │ │ └── Contents.json
│ │ │ ├── blue_6.colorset
│ │ │ └── Contents.json
│ │ │ ├── brown.colorset
│ │ │ └── Contents.json
│ │ │ ├── gray.colorset
│ │ │ └── Contents.json
│ │ │ ├── gray_2.colorset
│ │ │ └── Contents.json
│ │ │ ├── gray_3.colorset
│ │ │ └── Contents.json
│ │ │ ├── gray_4.colorset
│ │ │ └── Contents.json
│ │ │ ├── green.colorset
│ │ │ └── Contents.json
│ │ │ ├── green_2.colorset
│ │ │ └── Contents.json
│ │ │ ├── green_3.colorset
│ │ │ └── Contents.json
│ │ │ ├── green_4.colorset
│ │ │ └── Contents.json
│ │ │ ├── red.colorset
│ │ │ └── Contents.json
│ │ │ ├── red_2.colorset
│ │ │ └── Contents.json
│ │ │ ├── red_3.colorset
│ │ │ └── Contents.json
│ │ │ ├── white.colorset
│ │ │ └── Contents.json
│ │ │ ├── yellow.colorset
│ │ │ └── Contents.json
│ │ │ ├── yellow_2.colorset
│ │ │ └── Contents.json
│ │ │ ├── yellow_3.colorset
│ │ │ └── Contents.json
│ │ │ ├── yellow_4.colorset
│ │ │ └── Contents.json
│ │ │ └── yellow_5.colorset
│ │ │ └── Contents.json
│ ├── Configs
│ │ ├── Target.xcconfig
│ │ └── Project.xcconfig
│ ├── Project.swift
│ └── Sources
│ │ └── Colors.swift
├── Workspace.swift
├── CommonSwiftUi
│ ├── Sources
│ │ ├── Elements
│ │ │ ├── AppProgress.swift
│ │ │ ├── AppSurface.swift
│ │ │ ├── AppAvatar.swift
│ │ │ ├── AppScreen.swift
│ │ │ ├── AppDeepLink.swift
│ │ │ ├── AppIcon.swift
│ │ │ ├── AppText.swift
│ │ │ ├── AppImage.swift
│ │ │ └── AppButton.swift
│ │ ├── Theme
│ │ │ ├── Background.swift
│ │ │ ├── Typography.swift
│ │ │ ├── ContentColor.swift
│ │ │ ├── Theme.swift
│ │ │ └── Shape.swift
│ │ └── Test
│ │ │ └── CustomSwiftUiTestRuleWrapper.swift
│ ├── Configs
│ │ ├── Target.xcconfig
│ │ └── Project.xcconfig
│ └── Project.swift
├── Feed
│ ├── Configs
│ │ ├── Target.xcconfig
│ │ ├── UiTestHost-Target.xcconfig
│ │ └── Project.xcconfig
│ └── Project.swift
├── Post
│ ├── Configs
│ │ ├── Target.xcconfig
│ │ ├── UiTestHost-Target.xcconfig
│ │ └── Project.xcconfig
│ ├── Project.swift
│ └── Tests
│ │ └── Host
│ │ └── PostTestHostApp.swift
├── CommonKotlinMultiplatform
│ └── Project.swift
└── CommonFirebase
│ └── Project.swift
├── android
├── common
│ ├── design
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── res
│ │ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── colors.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ └── build.gradle.kts
│ ├── navigation
│ │ ├── src
│ │ │ └── main
│ │ │ │ ├── res
│ │ │ │ └── values
│ │ │ │ │ └── strings.xml
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── android
│ │ │ │ └── common
│ │ │ │ └── navigation
│ │ │ │ ├── Navigator.kt
│ │ │ │ ├── NavigationModule.kt
│ │ │ │ └── RealNavigator.kt
│ │ └── build.gradle.kts
│ ├── compose-test
│ │ ├── src
│ │ │ └── main
│ │ │ │ ├── res
│ │ │ │ └── values
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── android
│ │ │ │ │ └── common
│ │ │ │ │ └── composetest
│ │ │ │ │ ├── ComposeTestActivity.kt
│ │ │ │ │ ├── CommonComposeTestClass.kt
│ │ │ │ │ ├── Finders.kt
│ │ │ │ │ └── CustomComposeTestRule.kt
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle.kts
│ ├── compose
│ │ ├── src
│ │ │ └── main
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── android
│ │ │ │ └── common
│ │ │ │ └── compose
│ │ │ │ ├── elements
│ │ │ │ ├── AppRipple.kt
│ │ │ │ ├── AppProgress.kt
│ │ │ │ ├── AppScreen.kt
│ │ │ │ ├── AppHtmlText.kt
│ │ │ │ ├── AppImage.kt
│ │ │ │ ├── AppIcon.kt
│ │ │ │ ├── AppSurface.kt
│ │ │ │ ├── AppText.kt
│ │ │ │ ├── AppPullRefresh.kt
│ │ │ │ ├── avatar
│ │ │ │ │ ├── AppAvatar.kt
│ │ │ │ │ └── ColorGenerator.kt
│ │ │ │ ├── AppBar.kt
│ │ │ │ └── AppButton.kt
│ │ │ │ ├── CommonComposeActivity.kt
│ │ │ │ └── theme
│ │ │ │ ├── Background.kt
│ │ │ │ ├── Typography.kt
│ │ │ │ ├── ContentColor.kt
│ │ │ │ ├── Shape.kt
│ │ │ │ └── Theme.kt
│ │ └── build.gradle.kts
│ ├── firebase
│ │ └── build.gradle.kts
│ └── test
│ │ └── build.gradle.kts
├── post
│ ├── feature
│ │ ├── src
│ │ │ └── main
│ │ │ │ ├── res
│ │ │ │ └── values
│ │ │ │ │ └── strings.xml
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle.kts
│ └── test-fixtures
│ │ └── build.gradle.kts
├── feed
│ ├── feature
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ │ └── AndroidManifest.xml
│ └── test-fixtures
│ │ └── build.gradle.kts
└── app
│ ├── src
│ └── main
│ │ ├── kotlin
│ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── android
│ │ │ └── app
│ │ │ ├── NewsfeedApp.kt
│ │ │ └── DependencyInjector.kt
│ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle-plugins
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradle.properties
├── settings.gradle.kts
└── conventions
│ └── src
│ └── main
│ └── kotlin
│ └── com
│ └── gchristov
│ └── newsfeed
│ └── gradleplugins
│ ├── multiplatform
│ ├── MplBuildConfigPlugin.kt
│ ├── MplModulePlugin.kt
│ ├── MplFeaturePlugin.kt
│ ├── MplDataPlugin.kt
│ └── MplBasePlugin.kt
│ ├── android
│ ├── AndroidBinaryPlugin.kt
│ ├── AndroidComposePlugin.kt
│ ├── AndroidFirebasePlugin.kt
│ ├── AndroidModulePlugin.kt
│ ├── AndroidFeaturePlugin.kt
│ └── AndroidBasePlugin.kt
│ ├── BaseExtensionExt.kt
│ └── ProjectExt.kt
├── multiplatform
├── common
│ ├── mvvm-test
│ │ ├── src
│ │ │ ├── commonMain
│ │ │ │ └── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── common
│ │ │ │ │ └── mvvmtest
│ │ │ │ │ └── CommonViewModelTestClass.kt
│ │ │ ├── iosMain
│ │ │ │ └── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── common
│ │ │ │ │ └── mvvmtest
│ │ │ │ │ └── CommonViewModelTestClass.kt
│ │ │ └── androidMain
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── common
│ │ │ │ └── mvvmtest
│ │ │ │ └── CommonViewModelTestClass.kt
│ │ └── build.gradle.kts
│ ├── kotlin
│ │ ├── src
│ │ │ ├── androidMain
│ │ │ │ └── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── common
│ │ │ │ │ └── kotlin
│ │ │ │ │ └── AppContext.kt
│ │ │ └── commonMain
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── common
│ │ │ │ └── kotlin
│ │ │ │ ├── di
│ │ │ │ ├── DependencyModule.kt
│ │ │ │ └── DependencyInjector.kt
│ │ │ │ ├── Log.kt
│ │ │ │ ├── Serializer.kt
│ │ │ │ └── MplCommonKotlinModule.kt
│ │ └── build.gradle.kts
│ ├── network
│ │ ├── src
│ │ │ ├── androidMain
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── commonMain
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── common
│ │ │ │ └── network
│ │ │ │ ├── NetworkConfig.kt
│ │ │ │ ├── MplCommonNetworkModule.kt
│ │ │ │ └── NetworkClient.kt
│ │ └── build.gradle.kts
│ ├── test
│ │ ├── src
│ │ │ └── commonMain
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── common
│ │ │ │ └── test
│ │ │ │ ├── FakeClock.kt
│ │ │ │ ├── FakeCoroutineDispatcher.kt
│ │ │ │ └── FakeResponse.kt
│ │ └── build.gradle.kts
│ ├── mvvm
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ ├── androidMain
│ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── common
│ │ │ │ └── mvvm
│ │ │ │ └── ViewModelFactory.kt
│ │ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── common
│ │ │ └── mvvm
│ │ │ └── CommonViewModel.kt
│ ├── firebase
│ │ ├── src
│ │ │ ├── androidMain
│ │ │ │ └── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── common
│ │ │ │ │ └── firebase
│ │ │ │ │ └── AndroidCommonFirebaseModule.kt
│ │ │ ├── iosMain
│ │ │ │ └── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── common
│ │ │ │ │ └── firebase
│ │ │ │ │ └── IosCommonFirebaseModule.kt
│ │ │ └── commonMain
│ │ │ │ └── kotlin
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── common
│ │ │ │ └── firebase
│ │ │ │ └── MplCommonFirebaseModule.kt
│ │ └── build.gradle.kts
│ └── persistence
│ │ ├── build.gradle.kts
│ │ └── src
│ │ ├── iosMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── common
│ │ │ └── persistence
│ │ │ └── IosCommonPersistenceModule.kt
│ │ ├── androidMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── common
│ │ │ └── persistence
│ │ │ └── AndroidCommonPersistenceModule.kt
│ │ └── commonMain
│ │ └── kotlin
│ │ └── com
│ │ └── gchristov
│ │ └── newsfeed
│ │ └── multiplatform
│ │ └── common
│ │ └── persistence
│ │ └── MplCommonPersistenceModule.kt
├── feed
│ ├── data
│ │ ├── src
│ │ │ └── commonMain
│ │ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── gchristov
│ │ │ │ │ └── newsfeed
│ │ │ │ │ └── multiplatform
│ │ │ │ │ └── feed
│ │ │ │ │ └── data
│ │ │ │ │ ├── model
│ │ │ │ │ ├── FeedFilter.kt
│ │ │ │ │ ├── SectionedFeed.kt
│ │ │ │ │ └── Feed.kt
│ │ │ │ │ ├── db
│ │ │ │ │ ├── DbFeedFilter.kt
│ │ │ │ │ └── FeedFilterMapper.kt
│ │ │ │ │ ├── api
│ │ │ │ │ └── ApiFeedResponse.kt
│ │ │ │ │ ├── FeedApi.kt
│ │ │ │ │ └── usecase
│ │ │ │ │ ├── FlattenSectionedFeedUseCase.kt
│ │ │ │ │ └── RedecorateSectionedFeedUseCase.kt
│ │ │ │ └── sqldelight
│ │ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── feed
│ │ │ │ └── data
│ │ │ │ └── FeedSqlDelightDatabase.sq
│ │ └── build.gradle.kts
│ ├── test-fixtures
│ │ └── build.gradle.kts
│ └── feature
│ │ ├── build.gradle.kts
│ │ └── src
│ │ └── commonMain
│ │ └── kotlin
│ │ └── com
│ │ └── gchristov
│ │ └── newsfeed
│ │ └── multiplatform
│ │ └── feed
│ │ └── feature
│ │ ├── SearchWidgetState.kt
│ │ └── MplFeedModule.kt
├── post
│ ├── test-fixtures
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── post
│ │ │ └── testfixtures
│ │ │ └── PostCreator.kt
│ ├── feature
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── commonMain
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── post
│ │ │ └── feature
│ │ │ ├── MplPostModule.kt
│ │ │ └── PostViewModel.kt
│ └── data
│ │ ├── src
│ │ └── commonMain
│ │ │ ├── sqldelight
│ │ │ └── com
│ │ │ │ └── gchristov
│ │ │ │ └── newsfeed
│ │ │ │ └── multiplatform
│ │ │ │ └── post
│ │ │ │ └── data
│ │ │ │ └── PostSqlDelightDatabase.sq
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── post
│ │ │ └── data
│ │ │ ├── model
│ │ │ └── Post.kt
│ │ │ ├── api
│ │ │ └── ApiPostResponse.kt
│ │ │ ├── PostApi.kt
│ │ │ └── usecase
│ │ │ └── EstimateReadingTimeMinutesUseCase.kt
│ │ └── build.gradle.kts
├── auth
│ └── data
│ │ ├── build.gradle.kts
│ │ └── src
│ │ └── commonMain
│ │ ├── sqldelight
│ │ └── com
│ │ │ └── gchristov
│ │ │ └── newsfeed
│ │ │ └── multiplatform
│ │ │ └── auth
│ │ │ └── data
│ │ │ └── AuthSqlDelightDatabase.sq
│ │ └── kotlin
│ │ └── com
│ │ └── gchristov
│ │ └── newsfeed
│ │ └── multiplatform
│ │ └── auth
│ │ └── data
│ │ ├── MplAuthDataModule.kt
│ │ └── AuthRepository.kt
└── umbrella
│ ├── src
│ └── commonMain
│ │ └── kotlin
│ │ └── com
│ │ └── gchristov
│ │ └── newsfeed
│ │ └── multiplatform
│ │ └── umbrella
│ │ ├── Dispatcher.kt
│ │ └── DependencyInjector.kt
│ └── build.gradle.kts
├── gradle.properties
├── .gitignore
├── .github
├── actions
│ ├── build-android
│ │ └── action.yml
│ ├── test-ios
│ │ └── action.yml
│ ├── setup-gradle
│ │ └── action.yml
│ ├── unit-test
│ │ └── action.yml
│ ├── setup-xcode
│ │ └── action.yml
│ ├── build-ios
│ │ └── action.yml
│ └── test-android
│ │ └── action.yml
└── workflows
│ └── staging-check.yml
├── tools
└── scripts
│ ├── secrets.sh
│ └── new_app_setup.sh
├── PULL_REQUEST_TEMPLATE.md
└── settings.gradle.kts
/ios/Tuist.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let tuist = Tuist()
4 |
--------------------------------------------------------------------------------
/android/common/design/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Newsfeed
3 |
4 |
--------------------------------------------------------------------------------
/ios/App/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/android/common/navigation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | news
3 |
4 |
--------------------------------------------------------------------------------
/gradle-plugins/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/gradle-plugins/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/ios/App/Resources/Assets.xcassets/AppIcon.appiconset/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/ios/App/Resources/Assets.xcassets/AppIcon.appiconset/app_icon.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gchristov/newsfeed-kotlin-multiplatform/HEAD/android/common/design/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/common/navigation/src/main/kotlin/com/gchristov/newsfeed/android/common/navigation/Navigator.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.navigation
2 |
3 | interface Navigator {
4 | fun openPost(postId: String)
5 | }
--------------------------------------------------------------------------------
/gradle-plugins/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=2g
3 | org.gradle.caching=true
4 | org.gradle.configuration-cache=true
5 | org.gradle.parallel=true
6 |
7 | #Kotlin
8 | kotlin.code.style=official
--------------------------------------------------------------------------------
/multiplatform/common/mvvm-test/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/common/mvvmtest/CommonViewModelTestClass.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.mvvmtest
2 |
3 | expect open class CommonViewModelTestClass()
--------------------------------------------------------------------------------
/android/common/design/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.common.design"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/multiplatform/common/kotlin/src/androidMain/kotlin/com/gchristov/newsfeed/multiplatform/common/kotlin/AppContext.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.kotlin
2 |
3 | import android.content.Context
4 |
5 | lateinit var AppContext: Context
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/multiplatform/common/mvvm-test/src/iosMain/kotlin/com/gchristov/newsfeed/multiplatform/common/mvvmtest/CommonViewModelTestClass.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.mvvmtest
2 |
3 | actual open class CommonViewModelTestClass actual constructor()
--------------------------------------------------------------------------------
/android/common/compose-test/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
--------------------------------------------------------------------------------
/multiplatform/common/network/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle-plugins/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/android/common/compose-test/src/main/kotlin/com/gchristov/newsfeed/android/common/composetest/ComposeTestActivity.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.composetest
2 |
3 | import androidx.activity.ComponentActivity
4 |
5 | internal class ComposeTestActivity : ComponentActivity()
--------------------------------------------------------------------------------
/android/post/feature/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | %1$d min read
3 | Add to favourites
4 | Remove from favourites
5 |
6 |
--------------------------------------------------------------------------------
/multiplatform/common/network/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/common/network/NetworkConfig.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.network
2 |
3 | data class NetworkConfig(
4 | val guardianApiKey: String,
5 | val guardianApiUrl: String,
6 | )
7 |
--------------------------------------------------------------------------------
/android/common/compose-test/src/main/kotlin/com/gchristov/newsfeed/android/common/composetest/CommonComposeTestClass.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.composetest
2 |
3 | import org.junit.Rule
4 |
5 | open class CommonComposeTestClass {
6 | @get:Rule
7 | val composeRule = createCustomComposeRule()
8 | }
--------------------------------------------------------------------------------
/android/common/design/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/android/common/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.common.navigation"
8 | }
9 | }
10 |
11 | dependencies {
12 | implementation(projects.multiplatform.common.kotlin)
13 | }
14 |
--------------------------------------------------------------------------------
/ios/Workspace.swift:
--------------------------------------------------------------------------------
1 | import ProjectDescription
2 |
3 | let workspace = Workspace(
4 | name: "Newsfeed",
5 | projects: [
6 | "CommonSwiftUi",
7 | "CommonDesign",
8 | "CommonFirebase",
9 | "CommonKotlinMultiplatform",
10 | "Post",
11 | "Feed",
12 | "App",
13 | ]
14 | )
15 |
--------------------------------------------------------------------------------
/multiplatform/feed/data/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/feed/data/model/FeedFilter.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.feed.data.model
2 |
3 | data class FeedFilter(
4 | val query: String
5 | ) {
6 | companion object {
7 | val Default = FeedFilter(query = "fintech")
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/multiplatform/common/test/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/common/test/FakeClock.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.test
2 |
3 | import kotlinx.datetime.Clock
4 | import kotlinx.datetime.Instant
5 |
6 | object FakeClock : Clock {
7 | override fun now(): Instant = Instant.parse("2022-02-22T00:00:00Z")
8 | }
--------------------------------------------------------------------------------
/android/common/design/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #Gradle
2 | org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=2g
3 | org.gradle.caching=true
4 | org.gradle.configuration-cache=true
5 | org.gradle.parallel=true
6 |
7 | #Kotlin
8 | kotlin.code.style=official
9 | kotlin.native.ignoreDisabledTargets=true
10 |
11 | #Android
12 | android.useAndroidX=true
13 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/multiplatform/feed/data/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/feed/data/db/DbFeedFilter.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.feed.data.db
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | internal data class DbFeedFilter(
8 | @SerialName("query") val query: String,
9 | )
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | .idea
5 | .DS_Store
6 | /build
7 | */build
8 | build
9 | **/.build
10 | /captures
11 | .externalNativeBuild
12 | .cxx
13 | local.properties
14 | secrets.properties
15 | google-services.json
16 | GoogleService-Info.plist
17 | .kotlin
18 | *.xcworkspace
19 | Info.plist
20 | *.pbxproj
21 | *.xcodeproj
22 | Derived/
23 |
--------------------------------------------------------------------------------
/android/common/compose/src/main/kotlin/com/gchristov/newsfeed/android/common/compose/elements/AppRipple.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.compose.elements
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | fun rememberRipple(): Indication {
8 | return androidx.compose.material.ripple.rememberRipple()
9 | }
--------------------------------------------------------------------------------
/android/feed/feature/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.feature)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.feed.feature"
8 | }
9 | }
10 |
11 | dependencies {
12 | implementation(projects.multiplatform.feed.feature)
13 | androidTestImplementation(projects.android.feed.testFixtures)
14 | }
--------------------------------------------------------------------------------
/android/feed/test-fixtures/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.feed.testfixtures"
8 | }
9 | }
10 |
11 | dependencies {
12 | implementation(projects.android.common.composeTest)
13 | implementation(projects.android.feed.feature)
14 | }
15 |
--------------------------------------------------------------------------------
/android/post/feature/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.feature)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.post.feature"
8 | }
9 | }
10 |
11 | dependencies {
12 | implementation(projects.multiplatform.post.feature)
13 | androidTestImplementation(projects.android.post.testFixtures)
14 | }
--------------------------------------------------------------------------------
/android/post/test-fixtures/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.post.testfixtures"
8 | }
9 | }
10 |
11 | dependencies {
12 | implementation(projects.android.common.composeTest)
13 | implementation(projects.android.post.feature)
14 | }
15 |
--------------------------------------------------------------------------------
/multiplatform/feed/data/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/feed/data/db/FeedFilterMapper.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.feed.data.db
2 |
3 | import com.gchristov.newsfeed.multiplatform.feed.data.model.FeedFilter
4 |
5 | internal fun FeedFilter.toFeedFilter() = DbFeedFilter(query = query)
6 |
7 | internal fun DbFeedFilter.toFeedFilter() = FeedFilter(query = query)
--------------------------------------------------------------------------------
/multiplatform/common/mvvm/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.common.mvvm"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | api(libs.moko.mvvm.livedata)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/android/common/firebase/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.common.firebase"
8 | }
9 | }
10 |
11 | dependencies {
12 | api(platform(libs.google.firebase))
13 | implementation(libs.google.crashlytics)
14 | implementation(libs.google.analytics)
15 | }
16 |
--------------------------------------------------------------------------------
/android/common/compose-test/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/multiplatform/feed/test-fixtures/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.feed.testfixtures"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | implementation(projects.multiplatform.feed.data)
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/multiplatform/post/test-fixtures/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.post.testfixtures"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | implementation(projects.multiplatform.post.data)
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle-plugins/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
2 |
3 | dependencyResolutionManagement {
4 | repositories {
5 | google()
6 | gradlePluginPortal()
7 | }
8 | versionCatalogs {
9 | create("libs") {
10 | from(files("../gradle/libs.versions.toml"))
11 | }
12 | }
13 | }
14 |
15 | rootProject.name = "gradle-plugins"
16 |
17 | include(":conventions")
--------------------------------------------------------------------------------
/multiplatform/auth/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | val packageId = "com.gchristov.newsfeed.multiplatform.auth.data"
2 |
3 | plugins {
4 | alias(libs.plugins.newsfeed.mpl.data)
5 | }
6 |
7 | android {
8 | defaultConfig {
9 | namespace = packageId
10 | }
11 | }
12 |
13 | sqldelight {
14 | databases {
15 | create("AuthSqlDelightDatabase") {
16 | packageName.set(packageId)
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/android/feed/feature/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Added to favourites
4 | No results found. Please\ntry another search term
5 | This week
6 | Last week
7 | This month
8 |
--------------------------------------------------------------------------------
/multiplatform/common/test/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/common/test/FakeCoroutineDispatcher.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.test
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | /**
7 | * Use this dispatcher to execute coroutines instantly and sequentially in unit tests.
8 | */
9 | val FakeCoroutineDispatcher: CoroutineDispatcher = Dispatchers.Unconfined
--------------------------------------------------------------------------------
/gradle-plugins/conventions/src/main/kotlin/com/gchristov/newsfeed/gradleplugins/multiplatform/MplBuildConfigPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.gradleplugins.multiplatform
2 |
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 |
6 | class MplBuildConfigPlugin : Plugin {
7 | override fun apply(target: Project) {
8 | target.run {
9 | plugins.apply("com.codingfeline.buildkonfig")
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/multiplatform/post/feature/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.feature)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.post.feature"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | api(projects.multiplatform.post.data)
15 | api(projects.multiplatform.post.testFixtures)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/black.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.000",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/black_2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.082",
9 | "green" : "0.082",
10 | "red" : "0.082"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/black_3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.157",
9 | "green" : "0.153",
10 | "red" : "0.145"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/blue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.635",
9 | "green" : "0.122",
10 | "red" : "0.482"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/blue_2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.659",
9 | "green" : "0.176",
10 | "red" : "0.318"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/blue_3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.624",
9 | "green" : "0.247",
10 | "red" : "0.188"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/blue_4.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.824",
9 | "green" : "0.463",
10 | "red" : "0.098"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/blue_5.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.820",
9 | "green" : "0.533",
10 | "red" : "0.008"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/blue_6.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.655",
9 | "green" : "0.592",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/brown.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.216",
9 | "green" : "0.251",
10 | "red" : "0.365"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/gray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.380",
9 | "green" : "0.380",
10 | "red" : "0.380"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/gray_2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.392",
9 | "green" : "0.353",
10 | "red" : "0.271"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/gray_3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.753",
9 | "green" : "0.753",
10 | "red" : "0.753"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/gray_4.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.937",
9 | "green" : "0.937",
10 | "red" : "0.937"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/green.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.420",
9 | "green" : "0.475",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/green_2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.235",
9 | "green" : "0.557",
10 | "red" : "0.220"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/green_3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.220",
9 | "green" : "0.624",
10 | "red" : "0.408"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/green_4.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.169",
9 | "green" : "0.706",
10 | "red" : "0.686"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/red.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.376",
9 | "green" : "0.106",
10 | "red" : "0.847"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/red_2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.357",
9 | "green" : "0.094",
10 | "red" : "0.761"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/red_3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.184",
9 | "green" : "0.184",
10 | "red" : "0.827"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/white.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/yellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.800",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/yellow_2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.176",
9 | "green" : "0.753",
10 | "red" : "0.984"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/yellow_3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.627",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/yellow_4.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.486",
10 | "red" : "0.961"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/CommonDesign/Resources/Assets.xcassets/yellow_5.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.098",
9 | "green" : "0.290",
10 | "red" : "0.902"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/multiplatform/auth/data/src/commonMain/sqldelight/com/gchristov/newsfeed/multiplatform/auth/data/AuthSqlDelightDatabase.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE UserSession (
2 | userId TEXT NOT NULL PRIMARY KEY,
3 | userName TEXT NOT NULL
4 | );
5 | CREATE INDEX user_session_id ON UserSession(userId);
6 |
7 | clearSession:
8 | DELETE
9 | FROM UserSession;
10 |
11 | getSession:
12 | SELECT *
13 | FROM UserSession;
14 |
15 | insertSession:
16 | INSERT OR REPLACE INTO UserSession(userId, userName)
17 | VALUES (?, ?);
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/gchristov/newsfeed/android/app/NewsfeedApp.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.app
2 |
3 | import android.app.Application
4 | import com.gchristov.newsfeed.multiplatform.common.kotlin.di.DependencyInjector
5 |
6 | class NewsfeedApp : Application() {
7 | override fun onCreate() {
8 | super.onCreate()
9 | setupDependencyInjection()
10 | }
11 |
12 | private fun setupDependencyInjection() {
13 | DependencyInjector.initialise(this)
14 | }
15 | }
--------------------------------------------------------------------------------
/multiplatform/common/firebase/src/androidMain/kotlin/com/gchristov/newsfeed/multiplatform/common/firebase/AndroidCommonFirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.firebase
2 |
3 | import com.gchristov.newsfeed.multiplatform.common.kotlin.AppContext
4 | import dev.gitlive.firebase.Firebase
5 | import dev.gitlive.firebase.FirebaseApp
6 | import dev.gitlive.firebase.initialize
7 |
8 | internal actual fun provideFirebaseApp(): FirebaseApp =
9 | requireNotNull(Firebase.initialize(context = AppContext))
--------------------------------------------------------------------------------
/multiplatform/common/kotlin/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/common/kotlin/di/DependencyModule.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.kotlin.di
2 |
3 | import org.kodein.di.DI
4 |
5 | abstract class DependencyModule {
6 | abstract fun name(): String
7 |
8 | abstract fun bindDependencies(builder: DI.Builder)
9 |
10 | val module: DI.Module
11 | get() = DI.Module(name = name()) {
12 | bindDependencies(builder = this)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ios/CommonSwiftUi/Sources/Elements/AppProgress.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct AppCircularProgressIndicator: View {
4 | @EnvironmentObject var theme: Theme
5 | private let tint: Color?
6 |
7 | public init(tint: Color? = nil) {
8 | self.tint = tint
9 | }
10 |
11 | public var body: some View {
12 | ProgressView()
13 | .progressViewStyle(CircularProgressViewStyle(tint: tint ?? theme.contentColors.action))
14 | .accessibilityLabel("Loading")
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ios/CommonSwiftUi/Sources/Theme/Background.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import CommonDesign
3 |
4 | public struct Backgrounds {
5 | public let primary: Color
6 | public let surface: Color
7 | }
8 |
9 | func lightBackgrounds() -> Backgrounds {
10 | return Backgrounds(
11 | primary: Color.appGray4,
12 | surface: Color.appWhite
13 | )
14 | }
15 |
16 | func darkBackgrounds() -> Backgrounds {
17 | return Backgrounds(
18 | primary: Color.appBlack,
19 | surface: Color.appBlack2
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/multiplatform/common/mvvm-test/src/androidMain/kotlin/com/gchristov/newsfeed/multiplatform/common/mvvmtest/CommonViewModelTestClass.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.mvvmtest
2 |
3 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule
4 | import kotlinx.coroutines.ExperimentalCoroutinesApi
5 | import org.junit.Rule
6 |
7 | @ExperimentalCoroutinesApi
8 | actual open class CommonViewModelTestClass {
9 | // Allows testing of LiveData
10 | @get:Rule
11 | val rule = InstantTaskExecutorRule()
12 | }
--------------------------------------------------------------------------------
/multiplatform/feed/feature/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.feature)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.feed.feature"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | api(projects.multiplatform.feed.data)
15 | api(projects.multiplatform.feed.testFixtures)
16 | api(projects.multiplatform.post.testFixtures) // Needed for fake post repository
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/android/common/compose/src/main/kotlin/com/gchristov/newsfeed/android/common/compose/elements/AppProgress.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.compose.elements
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import com.gchristov.newsfeed.android.common.compose.theme.Theme
6 |
7 | @Composable
8 | fun AppCircularProgressIndicator(modifier: Modifier = Modifier) {
9 | androidx.compose.material.CircularProgressIndicator(
10 | modifier = modifier,
11 | color = Theme.contentColors.action,
12 | )
13 | }
--------------------------------------------------------------------------------
/ios/CommonSwiftUi/Sources/Theme/Typography.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct Typography {
4 | public let title: Font
5 | public let subtitle: Font
6 | public let body: Font
7 | public let bodyBold: Font
8 | public let caption: Font
9 | }
10 |
11 | func typography() -> Typography {
12 | return Typography(
13 | title: Font.system(size: 24),
14 | subtitle: Font.system(size: 15),
15 | body: Font.system(size: 18),
16 | bodyBold: Font.system(size: 18, weight: .bold),
17 | caption: Font.system(size: 12)
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/multiplatform/common/firebase/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.common.firebase"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | api(libs.gitlive.firebase.firestore)
15 | api(libs.gitlive.firebase.analytics)
16 | implementation(libs.touchlab.kermit.crashlytics)
17 | implementation(libs.touchlab.crashkios)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/multiplatform/post/data/src/commonMain/sqldelight/com/gchristov/newsfeed/multiplatform/post/data/PostSqlDelightDatabase.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE Post (
2 | id TEXT NOT NULL PRIMARY KEY,
3 | date TEXT NOT NULL,
4 | headline TEXT,
5 | body TEXT,
6 | thumbnail TEXT
7 | );
8 | CREATE INDEX post_id ON Post(id);
9 |
10 | deletePost:
11 | DELETE
12 | FROM Post
13 | WHERE Post.id == ?;
14 |
15 | selectPostWithId:
16 | SELECT *
17 | FROM Post
18 | WHERE Post.id == ?;
19 |
20 | insertPost:
21 | INSERT OR REPLACE INTO Post(id, date, headline, body, thumbnail)
22 | VALUES (?, ?, ?, ?, ?);
--------------------------------------------------------------------------------
/multiplatform/feed/feature/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/feed/feature/SearchWidgetState.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.feed.feature
2 |
3 | // We can model this with a sealed class and pass in the current value directly, eg:
4 | //
5 | // sealed class SearchBarState {
6 | // object Closed: SearchBarState()
7 | // data class Opened(val text: String)
8 | // }
9 | //
10 | // TODO: Remodel this and move to AppSearchBar
11 | // https://github.com/gchristov/newsfeed-kmm/issues/20
12 | enum class SearchWidgetState {
13 | OPENED,
14 | CLOSED
15 | }
--------------------------------------------------------------------------------
/multiplatform/post/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | val packageId = "com.gchristov.newsfeed.multiplatform.post.data"
2 |
3 | plugins {
4 | alias(libs.plugins.newsfeed.mpl.data)
5 | }
6 |
7 | android {
8 | defaultConfig {
9 | namespace = packageId
10 | }
11 | }
12 |
13 | sqldelight {
14 | databases {
15 | create("PostSqlDelightDatabase") {
16 | packageName.set(packageId)
17 | }
18 | }
19 | }
20 |
21 | kotlin {
22 | sourceSets {
23 | commonMain.dependencies {
24 | implementation(projects.multiplatform.common.firebase)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/android/common/compose/src/main/kotlin/com/gchristov/newsfeed/android/common/compose/elements/AppScreen.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.compose.elements
2 |
3 | import androidx.compose.material.Scaffold
4 | import androidx.compose.runtime.Composable
5 | import com.gchristov.newsfeed.android.common.compose.theme.Theme
6 |
7 | @Composable
8 | fun AppScreen(
9 | topBar: @Composable () -> Unit = {},
10 | content: @Composable () -> Unit
11 | ) {
12 | Scaffold(
13 | topBar = topBar,
14 | backgroundColor = Theme.backgrounds.primary,
15 | ) {
16 | content()
17 | }
18 | }
--------------------------------------------------------------------------------
/multiplatform/common/firebase/src/iosMain/kotlin/com/gchristov/newsfeed/multiplatform/common/firebase/IosCommonFirebaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.firebase
2 |
3 | import co.touchlab.crashkios.crashlytics.setCrashlyticsUnhandledExceptionHook
4 | import dev.gitlive.firebase.Firebase
5 | import dev.gitlive.firebase.FirebaseApp
6 | import dev.gitlive.firebase.initialize
7 |
8 | internal actual fun provideFirebaseApp(): FirebaseApp {
9 | // Allows catching unhandled exceptions on iOS
10 | setCrashlyticsUnhandledExceptionHook()
11 | return requireNotNull(Firebase.initialize())
12 | }
--------------------------------------------------------------------------------
/android/common/compose-test/src/main/kotlin/com/gchristov/newsfeed/android/common/composetest/Finders.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.composetest
2 |
3 | import androidx.compose.ui.semantics.ProgressBarRangeInfo
4 | import androidx.compose.ui.test.SemanticsNodeInteraction
5 | import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
6 | import androidx.compose.ui.test.hasProgressBarRangeInfo
7 |
8 | /* Indeterminate progress */
9 |
10 | fun SemanticsNodeInteractionsProvider.onIndeterminateProgress(): SemanticsNodeInteraction {
11 | return onNode(hasProgressBarRangeInfo(ProgressBarRangeInfo.Indeterminate))
12 | }
--------------------------------------------------------------------------------
/ios/CommonSwiftUi/Sources/Elements/AppSurface.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct AppSurface: View where Content: View {
4 | @EnvironmentObject var theme: Theme
5 | private let content: () -> Content
6 |
7 | public init(@ViewBuilder content: @escaping () -> Content) {
8 | self.content = content
9 | }
10 |
11 | public var body: some View {
12 | ZStack {
13 | content()
14 | }
15 | .padding(16)
16 | .background(theme.backgrounds.surface)
17 | .clipShape(theme.shapes.surface)
18 | .shadow(radius: 2, x: 0.2, y: 0.5)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/android/common/test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.android.base) // Avoids circular dependencies in android-module-plugin
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.android.common.test"
8 | }
9 | }
10 |
11 | /*
12 | This module is used in other test modules. Common dependencies are linked to the 'main'
13 | source sets, and marked as `api`, rather than 'test'. This is because 'test' source-specific
14 | dependencies and code are local to the relevant module and cannot be accesses by other modules.
15 | */
16 | dependencies {
17 | api(libs.junit)
18 | }
19 |
--------------------------------------------------------------------------------
/gradle-plugins/conventions/src/main/kotlin/com/gchristov/newsfeed/gradleplugins/android/AndroidBinaryPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.gradleplugins.android
2 |
3 | import com.gchristov.newsfeed.gradleplugins.libs
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 |
7 | class AndroidApplicationBinaryPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with (target) {
10 | with (plugins) {
11 | apply("com.android.application")
12 | apply(libs.findPlugin("newsfeed-android-base").get().get().pluginId)
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/multiplatform/common/kotlin/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/common/kotlin/Log.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.kotlin
2 |
3 | import co.touchlab.kermit.Logger
4 |
5 | fun Logger.debug(tag: String?, message: String) = d(logMessage(tag, message))
6 |
7 | fun Logger.debug(tag: String?, throwable: Throwable, message: () -> String) = d(throwable) { logMessage(tag, message()) }
8 |
9 | fun Logger.error(tag: String?, throwable: Throwable, message: () -> String) = e(throwable) { logMessage(tag, message()) }
10 |
11 | private fun logMessage(tag: String?, message: String) = "[${tag ?: "Anonymous"}] $message"
--------------------------------------------------------------------------------
/multiplatform/post/test-fixtures/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/post/testfixtures/PostCreator.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.post.testfixtures
2 |
3 | import com.gchristov.newsfeed.multiplatform.post.data.Post
4 |
5 | object PostCreator {
6 | fun post(
7 | id: String = "post_123",
8 | title: String = "Post Title",
9 | body: String = "This is a sample post body",
10 | date: String = "2022-02-21T00:00:00Z",
11 | ): Post = Post(
12 | id = id,
13 | date = date,
14 | headline = title,
15 | body = body,
16 | thumbnail = null,
17 | )
18 | }
--------------------------------------------------------------------------------
/gradle-plugins/conventions/src/main/kotlin/com/gchristov/newsfeed/gradleplugins/BaseExtensionExt.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.gradleplugins
2 |
3 | import com.android.build.gradle.BaseExtension
4 |
5 | internal fun BaseExtension.configureAndroid() {
6 | compileSdkVersion(34)
7 | defaultConfig {
8 | minSdk = 21
9 | targetSdk = 34
10 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
11 | }
12 | // Resolves "N files found with path 'META-INF/XXX'" errors
13 | packagingOptions {
14 | resources.excludes.add("META-INF/AL2.0")
15 | resources.excludes.add("META-INF/LGPL2.1")
16 | }
17 | }
--------------------------------------------------------------------------------
/multiplatform/umbrella/src/commonMain/kotlin/com/gchristov/newsfeed/multiplatform/umbrella/Dispatcher.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.umbrella
2 |
3 | import kotlinx.coroutines.Dispatchers
4 |
5 | /**
6 | * Exposes coroutine dispatchers to native targets,
7 | *
8 | * Exposing the coroutines dependency directly trigger some link errors so we use a simple wrapper.
9 | */
10 | @Suppress("unused")
11 | object Dispatcher {
12 | @Suppress("unused")
13 | val Default = Dispatchers.Default
14 |
15 | @Suppress("unused")
16 | val Main = Dispatchers.Main
17 |
18 | @Suppress("unused")
19 | val Unconfined = Dispatchers.Unconfined
20 | }
--------------------------------------------------------------------------------
/.github/actions/build-android/action.yml:
--------------------------------------------------------------------------------
1 | name: 'build-android'
2 | runs:
3 | using: "composite"
4 | steps:
5 | - name: Set up Gradle
6 | uses: ./.github/actions/setup-gradle
7 | - name: Build Android
8 | shell: bash
9 | run: |
10 | set -Eeuo pipefail
11 | ./gradlew android:app:assembleDebug
12 | - name: Artifacts
13 | uses: actions/upload-artifact@v4
14 | if: always() # Ensure all artifacts are collected, even after errors
15 | with:
16 | name: Android Build
17 | path: |
18 | **/*.apk
19 | **/build
20 | **/secrets.properties
21 | **/google-services.json
--------------------------------------------------------------------------------
/.github/actions/test-ios/action.yml:
--------------------------------------------------------------------------------
1 | name: 'test-ios'
2 | runs:
3 | using: "composite"
4 | steps:
5 | - name: Set up Gradle
6 | uses: ./.github/actions/setup-gradle
7 | - name: Set up Xcode
8 | uses: ./.github/actions/setup-xcode
9 | - name: Test iOS
10 | shell: bash
11 | run: |
12 | set -Eeuo pipefail
13 | cd ios
14 | tuist test
15 | cd ..
16 | - name: Artifacts
17 | uses: actions/upload-artifact@v4
18 | if: always() # Ensure all artifacts are collected, even after errors
19 | with:
20 | name: iOS Tests
21 | path: |
22 | /Users/runner/.local/state/tuist/**/*.log
--------------------------------------------------------------------------------
/tools/scripts/secrets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | # Exports required CI environment secrets to local secrets so that the project can use them.
4 | # Should be invoked from the root of the project as all paths are relative.
5 |
6 | # network credentials (the > is intentional to append to a file)
7 | echo GUARDIAN_API_KEY="$GUARDIAN_API_KEY" >> multiplatform/common/network/secrets.properties
8 | echo GUARDIAN_API_URL="$GUARDIAN_API_URL" >> multiplatform/common/network/secrets.properties
9 |
10 | # Firebase credentials
11 | echo "$GOOGLE_SERVICES_JSON" >> android/app/google-services.json
12 | echo "$GOOGLE_SERVICE_INFO_PLIST" >> ios/App/Resources/GoogleService-Info.plist
13 |
--------------------------------------------------------------------------------
/ios/App/Configs/Target.xcconfig:
--------------------------------------------------------------------------------
1 | ASSETCATALOG_COMPILER_APPICON_NAME=AppIcon
2 | CODE_SIGN_STYLE=Automatic
3 | DEVELOPMENT_TEAM=5G893F5HW7
4 | ENABLE_PREVIEWS=YES
5 | INFOPLIST_FILE=app/Info.plist
6 | OTHER_LDFLAGS=
7 | PRODUCT_BUNDLE_IDENTIFIER=com.gchristov.newsfeed
8 | PRODUCT_NAME=$(TARGET_NAME)
9 | SUPPORTED_PLATFORMS=iphoneos iphonesimulator
10 | SUPPORTS_MACCATALYST=NO
11 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD=NO
12 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD=NO
13 | SWIFT_VERSION=5.0
14 | TARGETED_DEVICE_FAMILY=1
15 |
16 | LD_RUNPATH_SEARCH_PATHS[config=Debug]=$(inherited) @executable_path/Frameworks
17 | LD_RUNPATH_SEARCH_PATHS[config=Release]=$(inherited) @executable_path/Frameworks
--------------------------------------------------------------------------------
/multiplatform/common/persistence/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.common.persistence"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | implementation(libs.sqlDelight.core)
15 | api(libs.multiplatformSettings)
16 | }
17 | androidMain.dependencies {
18 | implementation(libs.sqlDelight.android)
19 | }
20 | iosMain.dependencies {
21 | implementation(libs.sqlDelight.native)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/CommonSwiftUi/Sources/Elements/AppAvatar.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import LetterAvatarKit
3 |
4 | public struct AppAvatar: View {
5 | private let name: String
6 | private let size: CGSize
7 |
8 | public init(
9 | name: String,
10 | size: CGSize
11 | ) {
12 | self.name = name
13 | self.size = size
14 | }
15 |
16 | public var body: some View {
17 | let avatar = LetterAvatarMaker()
18 | .setCircle(true)
19 | .setUsername(name)
20 | .useSingleLetter(true)
21 | .setSize(size)
22 | .build() ?? UIImage()
23 |
24 | Image(uiImage: avatar)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/multiplatform/common/kotlin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.base)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.common.kotlin"
8 | }
9 | }
10 |
11 | kotlin {
12 | sourceSets {
13 | commonMain.dependencies {
14 | api(libs.kodein)
15 | api(libs.touchlab.kermit)
16 | api(libs.kotlinx.coroutines.core)
17 | api(libs.kotlinx.datetime)
18 | api(libs.kotlinx.serialization)
19 | api(libs.arrow.core)
20 | }
21 | androidMain.dependencies {
22 | api(libs.kotlinx.coroutines.android)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/common/navigation/src/main/kotlin/com/gchristov/newsfeed/android/common/navigation/NavigationModule.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.android.common.navigation
2 |
3 | import android.content.Context
4 | import com.gchristov.newsfeed.multiplatform.common.kotlin.di.DependencyModule
5 | import org.kodein.di.DI
6 | import org.kodein.di.bindFactory
7 |
8 | object NavigationModule : DependencyModule() {
9 | override fun name() = "common-navigation"
10 |
11 | override fun bindDependencies(builder: DI.Builder) {
12 | builder.apply {
13 | bindFactory { context: Context ->
14 | RealNavigator(context = context)
15 | }
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/multiplatform/common/mvvm-test/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.newsfeed.mpl.module)
3 | }
4 |
5 | android {
6 | defaultConfig {
7 | namespace = "com.gchristov.newsfeed.multiplatform.common.mvvmtest"
8 | }
9 | }
10 |
11 | kotlin {
12 | /*
13 | This module is used in other test modules. Common dependencies are linked to the 'main'
14 | source sets, and marked as `api`, rather than 'test'. This is because 'test' source-specific
15 | dependencies and code are local to the relevant module and cannot be accesses by other modules.
16 | */
17 | sourceSets {
18 | commonMain.dependencies {
19 | api(libs.moko.mvvm.test)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/multiplatform/common/persistence/src/iosMain/kotlin/com/gchristov/newsfeed/multiplatform/common/persistence/IosCommonPersistenceModule.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.multiplatform.common.persistence
2 |
3 | import app.cash.sqldelight.db.SqlDriver
4 | import app.cash.sqldelight.driver.native.NativeSqliteDriver
5 | import com.russhwolf.settings.NSUserDefaultsSettings
6 | import com.russhwolf.settings.Settings
7 |
8 | internal actual fun provideSqlDriver(properties: SqlDriverProperties): SqlDriver =
9 | NativeSqliteDriver(
10 | schema = properties.schema,
11 | name = properties.databaseName
12 | )
13 |
14 | internal actual fun provideSharedPreferences(): Settings = NSUserDefaultsSettings.Factory().create()
--------------------------------------------------------------------------------
/gradle-plugins/conventions/src/main/kotlin/com/gchristov/newsfeed/gradleplugins/android/AndroidComposePlugin.kt:
--------------------------------------------------------------------------------
1 | package com.gchristov.newsfeed.gradleplugins.android
2 |
3 | import com.android.build.gradle.BaseExtension
4 | import org.gradle.api.Plugin
5 | import org.gradle.api.Project
6 | import org.gradle.kotlin.dsl.configure
7 |
8 | class AndroidComposePlugin : Plugin {
9 | override fun apply(target: Project) {
10 | with(target) {
11 | extensions.configure {
12 | buildFeatures.compose = true
13 | with (plugins) {
14 | apply("org.jetbrains.kotlin.plugin.compose")
15 | }
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/multiplatform/feed/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | val packageId = "com.gchristov.newsfeed.multiplatform.feed.data"
2 |
3 | plugins {
4 | alias(libs.plugins.newsfeed.mpl.data)
5 | }
6 |
7 | android {
8 | defaultConfig {
9 | namespace = packageId
10 | }
11 | }
12 |
13 | kotlin {
14 | sourceSets {
15 | commonMain.dependencies {
16 | api(projects.multiplatform.post.data)
17 | implementation(projects.multiplatform.auth.data)
18 | implementation(projects.multiplatform.common.firebase)
19 | }
20 | }
21 | }
22 |
23 | sqldelight {
24 | databases {
25 | create("FeedSqlDelightDatabase") {
26 | packageName.set(packageId)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ios/CommonSwiftUi/Sources/Elements/AppScreen.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct AppScreen: View where Content: View {
4 | @EnvironmentObject var theme: Theme
5 | private let content: () -> Content
6 |
7 | public init(@ViewBuilder content: @escaping () -> Content) {
8 | self.content = content
9 | }
10 |
11 | public var body: some View {
12 | ZStack {
13 | content()
14 | }
15 | .frame(
16 | minWidth: 0,
17 | maxWidth: .infinity,
18 | minHeight: 0,
19 | maxHeight: .infinity
20 | )
21 | .background(theme.backgrounds.primary.ignoresSafeArea()) // Safe are includes navigation view
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## What does this pull request change?
4 |
5 |
6 |
7 | ## Demo
8 |
9 |
10 |
11 | Before|After
12 | -|-
13 |