-
-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spezi Alignment #189
base: main
Are you sure you want to change the base?
Spezi Alignment #189
Conversation
BasicTextField( | ||
value = value, | ||
onValueChange = onValueChange, | ||
modifier = modifier.testIdentifier( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this wrapping to just set the test identifier is not needed, on demand, the test tag can be set on caller side and not internally as this way we are making it impossible for clients to set their own test tag as we always overwrite and they would have to know the internal implementation details of this component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, let's remove both all NameTextField and NameFieldRow variations, as they do not provide a lot of functionality anyways.
import androidx.compose.ui.text.input.KeyboardCapitalization | ||
import androidx.compose.ui.text.input.KeyboardType | ||
|
||
val KeyboardOptions.Companion.NameDefault |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be marked internal and moved under /internal pacakge
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may want to keep exposing this as it is actually the only real override for the NameTextField - so replacing a NameTextField from Swift to Kotlin would be using a regular TextField and setting this one value.
@@ -0,0 +1,141 @@ | |||
package edu.stanford.spezi.ui.personalinfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A general comment about this module (can be ignored since we want to align with iOS)
I see the whole setup of wrapping and duplicating all params of base text field components to just achieve a row layout not ideal and flexible. We would have to go for the same replications in case we would need to support a column layout of description and text fields. And again the same replications if we would need to support RTL languages.
Additionally, the description content seems a bit redundant to me and deviating from material design while all the text fields support already the label setup. While the text fields will be usually put in a column / lazy column, with this approach we can't guarantee a consistent layout unless we set fix weights to the description and text fields. See the difference between the current approach vs using material design components directly

Also, the approach with manipulating the kproperty of the builder I see not needed and not common in android. Having to do this kind of builder setup for every component as we are doing for person name is a solution that does not scale. Compose is already providing the base setup and every use case can do that directly based on its design requirements. I can share this setup that I did in heart beat in a single file / component that support three styles: https://github.com/StanfordSpezi/SpeziKt/pull/143/files#diff-2010cd3c435eaece095576f781cc7cdccc913dbeb8078505fedf39bac6621a2bR33

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DescriptionGridRow, NameTextField and NameFieldRow can be removed from this package.
import edu.stanford.spezi.ui.SpeziTheme | ||
import edu.stanford.spezi.ui.StringResource | ||
import edu.stanford.spezi.ui.ThemePreviews | ||
import edu.stanford.spezi.ui.lighten | ||
import kotlin.math.min | ||
|
||
interface UserProfileImageLoader { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's consider including coil in a follow up. We can extend our Image resource with a case data class Remote(val url ...), and handle this internally to use coils AsyncImage. It also supports api for fallbacks and place holders in case loading fails. And we dont need to create custom loaders for every use case then. For instance, we can apply it for Contact use case also
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we can provide an AsyncImageResource in the ui-package and make use of it here - I would actually keep it separate from ImageResource though to make a clear separation between cases where we allow for asynchronous loading and others where async loading would need to be handled separately and we actually expect an image to be available immediately.
) { | ||
var size by remember { mutableStateOf(IntSize.Zero) } | ||
var loadedImage by remember(imageLoader) { mutableStateOf<ImageResource?>(null) } | ||
|
||
LaunchedEffect(Unit) { | ||
loadedImage = runCatching { imageLoader() } | ||
LaunchedEffect(imageLoader) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this setup would not be needed if we use coil, as it will make sure to launch coroutines and take care of caching internally
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above
// Properties | ||
|
||
@Suppress("detekt:VariableMinLength") | ||
internal val id: UUID = UUID() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feel free to adapt detekt config file to exclude id from the variable min length rule
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine with that
val engine = ValidationEngineImpl(rules = listOf(ValidationRule.nonEmpty)) | ||
val engine = ValidationEngineImpl( | ||
rules = listOf(ValidationRule.nonEmpty), | ||
coroutineScope = GlobalScope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use test scopes for tests, consider SpeziTestScope or TestScope directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, didn't know about that - sounds good!
import androidx.compose.runtime.Composable | ||
import androidx.compose.ui.Modifier | ||
|
||
interface ComposableContent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding
@Composable
fun Content() {
Content(Modifier)
}
in case of default modifiers, as default values are not supported in composable interface methods
} | ||
|
||
@Composable | ||
fun Content(modifier: Modifier, tint: Color) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider introducing this typealias https://github.com/StanfordSpezi/SpeziKt/pull/143/files#diff-5e1b1e9082809c2ba95dc5c5fbdf0b7d44d4167b9c9e8ea87dd50e1076a21290R19
See doc comments there, you can have than `tint: ComposeValue get() { Colors.primary } as an interface requirement with default value, this way we can stick to the base ComposableContent interface, and can extend the type instead of introducing new methods for every config option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine ImageResource to not really make assumptions about the appearance in UI, but more like an object to know how to get some image data... In the UserProfile for example, we want to use the image as a mask and then apply a custom tint that isn't provided by an app developer but instead set by spezi:ui-personalinfo.
We may want to think about having two interfaces ComposableContent
and ConfigurableComposableContent<Configuration>
maybe, where one allows to have a generic constraint with a given configuration to set some values during composition. In this case, we could then create a data class ImageResourceConfiguration
with the tint as one of the properties - or sth like that.
* | ||
* @see Contact | ||
* @see ContactOptionCard | ||
* @see AddressCard | ||
*/ | ||
@OptIn(ExperimentalLayoutApi::class) | ||
@Composable | ||
fun ContactComposable(contact: Contact, modifier: Modifier = Modifier) { | ||
fun Contact.Content(modifier: Modifier = Modifier) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anything against conforming Contact directly to ComposableContent? It is indeed a ui layer model as it already has ui properties such as image resource and string resources
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, just that we would then need to put it in the file of the Contact itself and wouldn't have separated UI code from Compose stuff, but actually I'm fine with moving it, if you want.
Spezi Alignment
♻️ Current situation & Problem
This pull request improves on the current implementation to realize an aligned API with considerate deviations for a platform-native feel. It aims to align the API both more to Kotlin guidelines/best practices while providing feature parity for non-platform-specific code in the Spezi packages
foundation-*
,core-*
andui-*
.⚙️ Release Notes
Add a bullet point list summary of the feature and possible migration guides if this is a breaking change so this section can be added to the release notes.
Include code snippets that provide examples of the feature implemented or links to the documentation if it appends or changes the public interface.
📚 Documentation
Please ensure that you properly document any additions in conformance to Spezi Documentation Guide.
You can use this section to describe your solution, but we encourage contributors to document your reasoning and changes using in-line documentation.
✅ Testing
Please ensure that the PR meets the testing requirements set by CodeCov and that new functionality is appropriately tested.
This section describes important information about the tests and why some elements might not be testable.
📝 Code of Conduct & Contributing Guidelines
By creating and submitting this pull request, you agree to follow our Code of Conduct and Contributing Guidelines: