项目作者: icerockdev

项目描述 :
Multiplatform UI DSL with screen management in common code for mobile (android & ios) Kotlin Multiplatform development
高级语言: Kotlin
项目地址: git://github.com/icerockdev/moko-widgets.git
创建时间: 2019-10-29T05:18:24Z
项目社区:https://github.com/icerockdev/moko-widgets

开源协议:Apache License 2.0

下载


moko-widgets
GitHub license Download kotlin-version

Mobile Kotlin widgets

This is a Kotlin MultiPlatform library that provides declarative UI and application screens management
in common code. You can implement full application for Android and iOS only from common code with it.

Sample Screen

Android iOS
Sample Android Sample iOS

Code of screen structure:

  1. class LoginScreen(
  2. private val theme: Theme,
  3. private val loginViewModelFactory: () -> LoginViewModel
  4. ) : WidgetScreen<Args.Empty>() {
  5. override fun createContentWidget() = with(theme) {
  6. val viewModel = getViewModel(loginViewModelFactory)
  7. constraint(size = WidgetSize.AsParent) {
  8. val logoImage = +image(
  9. size = WidgetSize.Const(SizeSpec.WrapContent, SizeSpec.WrapContent),
  10. image = const(Image.resource(MR.images.logo)),
  11. scaleType = ImageWidget.ScaleType.FIT
  12. )
  13. val emailInput = +input(
  14. size = WidgetSize.WidthAsParentHeightWrapContent,
  15. id = Id.EmailInputId,
  16. label = const("Email".desc() as StringDesc),
  17. field = viewModel.emailField,
  18. inputType = InputType.PHONE
  19. )
  20. val passwordInput = +input(
  21. size = WidgetSize.WidthAsParentHeightWrapContent,
  22. id = Id.PasswordInputId,
  23. label = const("Password".desc() as StringDesc),
  24. field = viewModel.passwordField
  25. )
  26. val loginButton = +button(
  27. size = WidgetSize.Const(SizeSpec.AsParent, SizeSpec.Exact(50f)),
  28. content = ButtonWidget.Content.Text(Value.data("Login".desc())),
  29. onTap = viewModel::onLoginPressed
  30. )
  31. val registerButton = +button(
  32. id = Id.RegistrationButtonId,
  33. size = WidgetSize.Const(SizeSpec.WrapContent, SizeSpec.Exact(40f)),
  34. content = ButtonWidget.Content.Text(Value.data("Registration".desc())),
  35. onTap = viewModel::onRegistrationPressed
  36. )
  37. val copyrightText = +text(
  38. size = WidgetSize.WrapContent,
  39. text = const("IceRock Development")
  40. )
  41. constraints {
  42. passwordInput centerYToCenterY root
  43. passwordInput leftRightToLeftRight root offset 16
  44. emailInput bottomToTop passwordInput offset 8
  45. emailInput leftRightToLeftRight root offset 16
  46. loginButton topToBottom passwordInput
  47. loginButton leftRightToLeftRight root
  48. registerButton topToBottom loginButton
  49. registerButton rightToRight root
  50. // logo image height must be automatic ?
  51. logoImage centerXToCenterX root
  52. logoImage.verticalCenterBetween(
  53. top = root.top,
  54. bottom = emailInput.top
  55. )
  56. copyrightText centerXToCenterX root
  57. copyrightText bottomToBottom root.safeArea offset 8
  58. }
  59. }
  60. }
  61. object Id {
  62. object EmailInputId : InputWidget.Id
  63. object PasswordInputId : InputWidget.Id
  64. object RegistrationButtonId : ButtonWidget.Id
  65. }
  66. }

Code of theme:

  1. val loginScreen = Theme(baseTheme) {
  2. factory[ConstraintWidget.DefaultCategory] = ConstraintViewFactory(
  3. padding = PaddingValues(16f),
  4. background = Background(
  5. fill = Fill.Solid(Colors.white)
  6. )
  7. )
  8. factory[InputWidget.DefaultCategory] = SystemInputViewFactory(
  9. margins = MarginValues(bottom = 8f),
  10. underLineColor = Color(0x000000DD),
  11. underLineFocusedColor = Color(0x3949ABFF),
  12. labelTextStyle = TextStyle(
  13. size = 12,
  14. color = Color(0x3949ABFF),
  15. fontStyle = FontStyle.BOLD
  16. ),
  17. errorTextStyle = TextStyle(
  18. size = 12,
  19. color = Color(0xB00020FF),
  20. fontStyle = FontStyle.BOLD
  21. ),
  22. textStyle = TextStyle(
  23. size = 16,
  24. color = Color(0x000000FF),
  25. fontStyle = FontStyle.MEDIUM
  26. )
  27. )
  28. val corners = platformSpecific(android = 8f, ios = 25f)
  29. factory[ButtonWidget.DefaultCategory] = SystemButtonViewFactory(
  30. margins = MarginValues(top = 32f),
  31. background = {
  32. val bg: (Color) -> Background = {
  33. Background(
  34. fill = Fill.Solid(it),
  35. shape = Shape.Rectangle(
  36. cornerRadius = corners
  37. )
  38. )
  39. }
  40. StateBackground(
  41. normal = bg(Color(0x6770e0FF)),
  42. pressed = bg(Color(0x6770e0EE)),
  43. disabled = bg(Color(0x6770e0BB))
  44. )
  45. }.invoke(),
  46. textStyle = TextStyle(
  47. color = Colors.white
  48. )
  49. )
  50. factory[LoginScreen.Id.RegistrationButtonId] = SystemButtonViewFactory(
  51. background = {
  52. val bg: (Color) -> Background = {
  53. Background(
  54. fill = Fill.Solid(it),
  55. shape = Shape.Rectangle(
  56. cornerRadius = corners
  57. )
  58. )
  59. }
  60. StateBackground(
  61. normal = bg(Color(0xFFFFFF00)),
  62. pressed = bg(Color(0xE7E7EEEE)),
  63. disabled = bg(Color(0x000000BB))
  64. )
  65. }.invoke(),
  66. margins = MarginValues(top = 16f),
  67. textStyle = TextStyle(
  68. color = Color(0x777889FF)
  69. ),
  70. androidElevationEnabled = false
  71. )
  72. }

Table of Contents

Features

  • compliance with platform rules;
  • declare structure, not rendering;
  • compile-time safety;
  • reactive data handling.

Requirements

  • Gradle version 6.8+
  • Android API 16+
  • iOS version 11.0+

Installation

root build.gradle

  1. allprojects {
  2. repositories {
  3. mavenCentral()
  4. }
  5. }

project build.gradle

  1. dependencies {
  2. commonMainApi("dev.icerock.moko:widgets:0.2.4")
  3. commonMainApi("dev.icerock.moko:widgets-bottomsheet:0.2.4") // show bottom sheets
  4. commonMainApi("dev.icerock.moko:widgets-collection:0.2.4") // collection widget
  5. commonMainApi("dev.icerock.moko:widgets-datetime-picker:0.2.4") // show datepicker
  6. commonMainApi("dev.icerock.moko:widgets-image-network:0.2.4") // images with load from url
  7. commonMainApi("dev.icerock.moko:widgets-sms:0.2.4") // input with sms autofill
  8. commonMainApi("dev.icerock.moko:widgets-media:0.2.4") // moko-media integration
  9. commonMainApi("dev.icerock.moko:widgets-permissions:0.2.4") // moko-permissions integration
  10. }

Codegen for new Widgets with @WidgetDef

root build.gradle

  1. buildscript {
  2. repositories {
  3. gradlePluginPortal()
  4. }
  5. dependencies {
  6. classpath "dev.icerock.moko.widgets:gradle-plugin:0.2.4"
  7. }
  8. }

project build.gradle

  1. apply plugin: "dev.icerock.mobile.multiplatform-widgets-generator" // must apply before kotlin-multiplatform plugin

Usage

Hello world

Multiplatform application definition at mpp-library/src/commonMain/kotlin/App.kt:

  1. class App : BaseApplication() {
  2. override fun setup(): ScreenDesc<Args.Empty> {
  3. val theme = Theme()
  4. return registerScreen(HelloWorldScreen::class) { HelloWorldScreen(theme) }
  5. }
  6. }

Screen definition mpp-library/src/commonMain/kotlin/HelloWorldScreen.kt:

  1. class HelloWorldScreen(
  2. private val theme: Theme
  3. ) : WidgetScreen<Args.Empty>() {
  4. override fun createContentWidget() = with(theme) {
  5. container(size = WidgetSize.AsParent) {
  6. center {
  7. text(
  8. size = WidgetSize.WrapContent,
  9. text = const("Hello World!")
  10. )
  11. }
  12. }
  13. }
  14. }

Result:

Android iOS
HelloWorld Android HelloWorld iOS

Configure styles

Setup theme config:

  1. val theme = Theme {
  2. factory[TextWidget.DefaultCategory] = SystemTextViewFactory(
  3. textStyle = TextStyle(
  4. size = 24,
  5. color = Colors.black
  6. ),
  7. padding = PaddingValues(padding = 16f)
  8. )
  9. }

Result:

Android iOS
Custom style Android Custom style iOS

Bind data to UI

  1. class TimerScreen(
  2. private val theme: Theme
  3. ) : WidgetScreen<Args.Empty>() {
  4. override fun createContentWidget(): Widget<WidgetSize.Const<SizeSpec.AsParent, SizeSpec.AsParent>> {
  5. val viewModel = getViewModel { TimerViewModel() }
  6. return with(theme) {
  7. container(size = WidgetSize.AsParent) {
  8. center {
  9. text(
  10. size = WidgetSize.WrapContent,
  11. text = viewModel.text
  12. )
  13. }
  14. }
  15. }
  16. }
  17. }
  18. class TimerViewModel : ViewModel() {
  19. private val iteration = MutableLiveData<Int>(0)
  20. val text: LiveData<StringDesc> = iteration.map { it.toString().desc() }
  21. init {
  22. viewModelScope.launch {
  23. while (isActive) {
  24. delay(1000)
  25. iteration.value = iteration.value + 1
  26. }
  27. }
  28. }
  29. }

Samples

Please see more examples in the sample directory.

Set Up Locally

Contributing

All development (both new features and bug fixes) is performed in the develop branch. This way master always contains the sources of the most recently released version. Please send PRs with bug fixes to the develop branch. Documentation fixes in the markdown files are an exception to this rule. They are updated directly in master.

The develop branch is pushed to master on release.

For more details on contributing please see the contributing guide.

License

  1. Copyright 2019 IceRock MAG Inc.
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.