Урок является продолжением MOKO Widgets #4 - screen actions. Для выполнения данного урока нужно иметь проект, полученный в результате выполнения предыдущего урока.
Результатом прошлого урока было приложение с навигацией, стилизацией экранов и различными действиями на экранах.
На этом уроке мы реализуем собственные фабрики виджетов на примере поля ввода телефона и кода.
mpp-library/src/commonMain/kotlin/org/example/mpp/PhoneInputViewFactory.kt
:
expect class PhoneInputViewFactory() : ViewFactory<InputWidget<out WidgetSize>>
mpp-library/src/androidMain/kotlin/org/example/mpp/PhoneInputViewFactory.kt
:
actual class PhoneInputViewFactory : ViewFactory<InputWidget<out WidgetSize>> {
override fun <WS : WidgetSize> build(
widget: InputWidget<out WidgetSize>,
size: WS,
viewFactoryContext: ViewFactoryContext
): ViewBundle<WS> {
val context = viewFactoryContext.androidContext
val lifecycleOwner = viewFactoryContext.lifecycleOwner
val editText = EditText(context).apply {
id = widget.id.androidId
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
widget.inputType?.also { applyInputType(it) }
setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) widget.field.validate()
}
addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(
s: CharSequence?,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (s == null) return
widget.field.data.value = s.toString()
}
})
}
widget.field.data.bind(lifecycleOwner) { data ->
if (editText.text?.toString() == data) return@bind
editText.setText(data)
}
widget.label.bind(lifecycleOwner) { editText.hint = it?.toString(context) }
widget.enabled?.bind(lifecycleOwner) { editText.isEnabled = it == true }
widget.maxLines?.bind(lifecycleOwner) { maxLines ->
when (maxLines) {
null -> editText.setSingleLine(false)
1 -> editText.setSingleLine(true)
else -> {
editText.setSingleLine(false)
editText.maxLines = maxLines
}
}
}
return ViewBundle(
view = editText,
size = size,
margins = null
)
}
}
mpp-library/src/iosMain/kotlin/org/example/mpp/PhoneInputViewFactory.kt
:
actual class PhoneInputViewFactory : ViewFactory<InputWidget<out WidgetSize>> {
override fun <WS : WidgetSize> build(
widget: InputWidget<out WidgetSize>,
size: WS,
viewFactoryContext: ViewFactoryContext
): ViewBundle<WS> {
val textField = UITextField(frame = CGRectZero.readValue()).apply {
translatesAutoresizingMaskIntoConstraints = false
applyInputTypeIfNeeded(widget.inputType)
clipsToBounds = true
}
val mask = widget.inputType?.mask
if (mask != null) {
val delegate = DefaultFormatterUITextFieldDelegate(
inputFormatter = DefaultTextFormatter(
textPattern = mask.toIosPattern(),
patternSymbol = '#'
)
)
textField.delegate = delegate
setAssociatedObject(textField, delegate)
}
textField.setEventHandler(UIControlEventEditingChanged) {
val currentValue = widget.field.data.value
val newValue = textField.text
if (currentValue != newValue) {
widget.field.data.value = newValue.orEmpty()
}
}
widget.enabled?.bind { textField.enabled = it }
widget.label.bind { textField.placeholder = it.localized() }
widget.field.data.bind { textField.text = it }
return ViewBundle(
view = textField,
size = size,
margins = null
)
}
}
mpp-library/src/commonMain/kotlin/org/example/mpp/App.kt
:
class App : BaseApplication() {
override fun setup(): ScreenDesc<Args.Empty> {
val theme = Theme() {
...
factory[InputPhoneScreen.Ids.Phone] = PhoneInputViewFactory()
}
...
}
...
}
mpp-library/src/commonMain/kotlin/org/example/mpp/CodeInputViewFactory.kt
:
expect class CodeInputViewFactory() : ViewFactory<InputWidget<out WidgetSize>>
buildSrc/src/main/kotlin/Deps.kt
:
object Deps {
...
object Libs {
object Android {
...
val otpView = AndroidLibrary(
name = "com.github.GoodieBag:Pinview:v1.4"
)
}
}
...
}
build.gradle.kts
:
...
allprojects {
repositories {
...
maven { url = uri("https://jitpack.io") }
}
...
}
...
mpp-library/build.gradle.kts
:
...
dependencies {
...
androidLibrary(Deps.Libs.Android.otpView)
}
...
mpp-library/src/androidMain/kotlin/org/example/mpp/CodeInputViewFactory.kt
:
actual class CodeInputViewFactory actual constructor() : ViewFactory<InputWidget<out WidgetSize>> {
override fun <WS : WidgetSize> build(
widget: InputWidget<out WidgetSize>,
size: WS,
viewFactoryContext: ViewFactoryContext
): ViewBundle<WS> {
val context = viewFactoryContext.androidContext
val lifecycleOwner = viewFactoryContext.lifecycleOwner
val editText = Pinview(context).apply {
id = widget.id.androidId
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) widget.field.validate()
}
setPinViewEventListener { pinview, fromUser ->
widget.field.data.value = pinview.value
widget.field.validate()
}
}
widget.field.data.bind(lifecycleOwner) { data ->
if (editText.value == data) return@bind
editText.value = data
}
widget.enabled?.bind(lifecycleOwner) { editText.isEnabled = it == true }
return ViewBundle(
view = editText,
size = size,
margins = null
)
}
}
buildSrc/build.gradle.kts
:
...
dependencies {
implementation("dev.icerock:mobile-multiplatform:0.5.0")
...
}
...
ios-app/Podfile
:
...
target 'ios-app' do
...
pod 'SVPinView', '1.0.7'
end
...
И требуется удалить автоустановку mpp-library
: cd .. && ./gradlew :mpp-library:syncMultiPlatformLibraryDebugFrameworkIosX64
.
mpp-library/build.gradle.kts
:
...
cocoaPods {
podsProject = file("../ios-app/Pods/Pods.xcodeproj")
pod("SVPinView")
}
mpp-library/src/iosMain/kotlin/org/example/mpp/CodeInputViewFactory.kt
:
actual class CodeInputViewFactory actual constructor() : ViewFactory<InputWidget<out WidgetSize>> {
override fun <WS : WidgetSize> build(
widget: InputWidget<out WidgetSize>,
size: WS,
viewFactoryContext: ViewFactoryContext
): ViewBundle<WS> {
val textField = SVPinView(frame = CGRectZero.readValue()).apply {
translatesAutoresizingMaskIntoConstraints = false
setPlaceholder("****")
heightAnchor.constraintEqualToConstant(80.0).active = true
}
// textField.setEventHandler(UIControlEventEditingChanged) {
// val currentValue = widget.field.data.value
// val newValue = textField.text
//
// if (currentValue != newValue) {
// widget.field.data.value = newValue.orEmpty()
// }
// }
// widget.field.data.bind { textField.text = it }
return ViewBundle(
view = textField,
size = size,
margins = null
)
}
}
mpp-library/src/commonMain/kotlin/org/example/mpp/App.kt
:
class App : BaseApplication() {
override fun setup(): ScreenDesc<Args.Empty> {
val theme = Theme() {
...
factory[InputCodeScreen.Ids.Code] = CodeInputViewFactory()
}
...
}
...
}