Skip to content

Commit ef96104

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feature/issue-55-custom-error-window
# Conflicts: # core/resources/src/commonMain/composeResources/values/strings.xml # feature/matcher/src/commonMain/kotlin/com/neoutils/neoregex/feature/matcher/MatcherScreen.kt
2 parents ec1ebe3 + 4f55832 commit ef96104

File tree

9 files changed

+337
-63
lines changed

9 files changed

+337
-63
lines changed

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/design-system/src/commonMain/kotlin/com/neoutils/neoregex/core/designsystem/theme/FontSizes.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ data class FontSizes(
3030
val medium: TextUnit = 16.sp,
3131
val default: TextUnit = 14.sp,
3232
val small: TextUnit = 12.sp,
33+
val tiny: TextUnit = 10.sp,
3334
)

core/design-system/src/commonMain/kotlin/com/neoutils/neoregex/core/designsystem/theme/Typhography.kt

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,105 +36,75 @@ fun NeoTypography(
3636
fontFamily = fontFamily,
3737
fontWeight = FontWeight.Bold,
3838
fontSize = 57.sp,
39-
lineHeight = 64.sp,
40-
letterSpacing = 0.sp
4139
),
4240
displayMedium = TextStyle(
4341
fontFamily = fontFamily,
4442
fontWeight = FontWeight.Bold,
4543
fontSize = 45.sp,
46-
lineHeight = 52.sp,
47-
letterSpacing = 0.sp
4844
),
4945
displaySmall = TextStyle(
5046
fontFamily = fontFamily,
5147
fontWeight = FontWeight.Bold,
5248
fontSize = 36.sp,
53-
lineHeight = 44.sp,
54-
letterSpacing = 0.sp
5549
),
5650
headlineLarge = TextStyle(
5751
fontFamily = fontFamily,
5852
fontWeight = FontWeight.Normal,
5953
fontSize = 32.sp,
60-
lineHeight = 40.sp,
61-
letterSpacing = 0.sp
6254
),
6355
headlineMedium = TextStyle(
6456
fontFamily = fontFamily,
6557
fontWeight = FontWeight.Normal,
6658
fontSize = 28.sp,
67-
lineHeight = 36.sp,
68-
letterSpacing = 0.sp
6959
),
7060
headlineSmall = TextStyle(
7161
fontFamily = fontFamily,
7262
fontWeight = FontWeight.Normal,
7363
fontSize = 24.sp,
74-
lineHeight = 32.sp,
75-
letterSpacing = 0.sp
7664
),
7765
titleLarge = TextStyle(
7866
fontFamily = fontFamily,
7967
fontWeight = FontWeight.SemiBold,
8068
fontSize = 22.sp,
81-
lineHeight = 28.sp,
82-
letterSpacing = 0.sp
8369
),
8470
titleMedium = TextStyle(
8571
fontFamily = fontFamily,
8672
fontWeight = FontWeight.Medium,
8773
fontSize = 16.sp,
88-
lineHeight = 24.sp,
89-
letterSpacing = 0.1.sp
9074
),
9175
titleSmall = TextStyle(
9276
fontFamily = fontFamily,
9377
fontWeight = FontWeight.Medium,
9478
fontSize = 14.sp,
95-
lineHeight = 20.sp,
96-
letterSpacing = 0.1.sp
9779
),
9880
bodyLarge = TextStyle(
9981
fontFamily = fontFamily,
10082
fontWeight = FontWeight.Normal,
10183
fontSize = 16.sp,
102-
lineHeight = 25.sp,
103-
letterSpacing = 0.5.sp
10484
),
10585
bodyMedium = TextStyle(
10686
fontFamily = fontFamily,
10787
fontWeight = FontWeight.Normal,
10888
fontSize = 14.sp,
109-
lineHeight = 20.sp,
110-
letterSpacing = 0.25.sp
11189
),
11290
bodySmall = TextStyle(
11391
fontFamily = fontFamily,
11492
fontWeight = FontWeight.Normal,
11593
fontSize = 12.sp,
116-
lineHeight = 16.sp,
117-
letterSpacing = 0.4.sp
11894
),
11995
labelLarge = TextStyle(
12096
fontFamily = fontFamily,
12197
fontWeight = FontWeight.Medium,
12298
fontSize = 14.sp,
123-
lineHeight = 20.sp,
124-
letterSpacing = 0.1.sp
12599
),
126100
labelMedium = TextStyle(
127101
fontFamily = fontFamily,
128102
fontWeight = FontWeight.Medium,
129103
fontSize = 12.sp,
130-
lineHeight = 16.sp,
131-
letterSpacing = 0.5.sp
132104
),
133105
labelSmall = TextStyle(
134106
fontFamily = fontFamily,
135107
fontWeight = FontWeight.Medium,
136108
fontSize = 11.sp,
137-
lineHeight = 16.sp,
138-
letterSpacing = 0.5.sp
139109
)
140110
)

core/resources/src/commonMain/composeResources/values/strings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,10 @@
2929
<string name="fatal_error_report_tab">Report error</string>
3030
<string name="fatal_error_report_btn">Report error</string>
3131
<string name="fatal_error_stack_trace_tab">Stack trace</string>
32+
<string name="insert_regex_hint">Enter regular expression</string>
33+
34+
<plurals name="match_result_infos">
35+
<item quantity="one">%1$s match (%2$s)</item>
36+
<item quantity="other">%1$s matches (%2$s)</item>
37+
</plurals>
3238
</resources>
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* NeoRegex.
3+
*
4+
* Copyright (C) 2024 Irineu A. Silva.
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package com.neoutils.neoregex.core.sharedui.component
20+
21+
import androidx.compose.animation.AnimatedVisibility
22+
import androidx.compose.animation.core.Animatable
23+
import androidx.compose.animation.core.VectorConverter
24+
import androidx.compose.animation.fadeIn
25+
import androidx.compose.animation.fadeOut
26+
import androidx.compose.foundation.background
27+
import androidx.compose.foundation.border
28+
import androidx.compose.foundation.gestures.detectDragGestures
29+
import androidx.compose.foundation.hoverable
30+
import androidx.compose.foundation.indication
31+
import androidx.compose.foundation.interaction.MutableInteractionSource
32+
import androidx.compose.foundation.layout.*
33+
import androidx.compose.foundation.shape.RoundedCornerShape
34+
import androidx.compose.material3.MaterialTheme.colorScheme
35+
import androidx.compose.material3.MaterialTheme.typography
36+
import androidx.compose.material3.Text
37+
import androidx.compose.material3.ripple
38+
import androidx.compose.runtime.*
39+
import androidx.compose.ui.Alignment
40+
import androidx.compose.ui.Modifier
41+
import androidx.compose.ui.geometry.Offset
42+
import androidx.compose.ui.geometry.Rect
43+
import androidx.compose.ui.input.pointer.pointerInput
44+
import androidx.compose.ui.layout.boundsInParent
45+
import androidx.compose.ui.layout.onGloballyPositioned
46+
import androidx.compose.ui.platform.LocalDensity
47+
import androidx.compose.ui.unit.dp
48+
import androidx.compose.ui.unit.round
49+
import com.neoutils.neoregex.core.common.platform.Platform
50+
import com.neoutils.neoregex.core.common.platform.platform
51+
import com.neoutils.neoregex.core.designsystem.theme.NeoTheme.dimensions
52+
import com.neoutils.neoregex.core.designsystem.theme.NeoTheme.fontSizes
53+
import com.neoutils.neoregex.core.resources.Res
54+
import com.neoutils.neoregex.core.resources.match_result_infos
55+
import kotlinx.coroutines.launch
56+
import org.jetbrains.compose.resources.pluralStringResource
57+
import kotlin.time.Duration
58+
import kotlin.time.DurationUnit
59+
60+
@Composable
61+
fun BoxWithConstraintsScope.MatchesInfos(infos: MatchesInfos) {
62+
63+
val density = LocalDensity.current
64+
65+
var isRunning by remember { mutableStateOf(false) }
66+
67+
val animateOffset = remember { Animatable(Offset.Zero, Offset.VectorConverter) }
68+
69+
var alignments by remember { mutableStateOf<Map<Alignment, Rect>>(mapOf()) }
70+
71+
var current by remember { mutableStateOf(Alignment.BottomEnd) }
72+
73+
// It needs to be a state to update the reference in pointerInput()
74+
val halfHeight by rememberUpdatedState(density.run { maxHeight.toPx() / 2f })
75+
76+
var targetRect by remember { mutableStateOf(Rect.Zero) }
77+
78+
var destination by remember { mutableStateOf(current) }
79+
80+
val scope = rememberCoroutineScope()
81+
82+
listOf(
83+
Alignment.TopEnd,
84+
Alignment.BottomEnd
85+
).forEach { alignment ->
86+
AlignmentTarget(
87+
alignment = alignment,
88+
isVisible = isRunning,
89+
isTarget = alignment == destination,
90+
modifier = Modifier
91+
.padding(dimensions.tiny)
92+
.size(density.run { targetRect.size.toDpSize() })
93+
.onGloballyPositioned {
94+
alignments = alignments + mapOf(
95+
alignment to it.boundsInParent()
96+
)
97+
}
98+
)
99+
}
100+
101+
Text(
102+
text = pluralStringResource(
103+
Res.plurals.match_result_infos,
104+
infos.matches,
105+
infos.matches,
106+
infos.duration.toString(
107+
unit = DurationUnit.MILLISECONDS,
108+
decimals = 3
109+
)
110+
),
111+
fontSize = fontSizes.tiny,
112+
style = typography.labelSmall,
113+
modifier = Modifier
114+
.align(current)
115+
.offset { animateOffset.value.round() }
116+
.padding(dimensions.tiny) // external
117+
.background(
118+
color = colorScheme.surfaceVariant,
119+
shape = RoundedCornerShape(dimensions.tiny)
120+
)
121+
.onGloballyPositioned {
122+
targetRect = it.boundsInParent()
123+
}
124+
.run {
125+
val hover = remember { MutableInteractionSource() }
126+
127+
hoverable(hover)
128+
.indication(
129+
interactionSource = hover,
130+
indication = ripple()
131+
)
132+
}
133+
.pointerInput(Unit) {
134+
detectDragGestures(
135+
onDragStart = {
136+
isRunning = true
137+
},
138+
onDragEnd = {
139+
scope.launch {
140+
141+
alignments[destination]?.let {
142+
animateOffset.snapTo(
143+
targetValue = targetRect.topLeft - it.topLeft
144+
)
145+
}
146+
147+
current = destination
148+
149+
animateOffset.animateTo(Offset.Zero)
150+
}
151+
152+
isRunning = false
153+
},
154+
onDrag = { changes, dragAmount ->
155+
changes.consume()
156+
157+
scope.launch {
158+
animateOffset.snapTo(
159+
targetValue = animateOffset.value + dragAmount
160+
)
161+
}
162+
163+
destination = when {
164+
current == Alignment.TopEnd &&
165+
targetRect.center.y > halfHeight -> Alignment.BottomEnd
166+
167+
current == Alignment.BottomEnd &&
168+
targetRect.center.y < halfHeight -> Alignment.TopEnd
169+
170+
else -> current
171+
}
172+
},
173+
onDragCancel = {
174+
scope.launch {
175+
animateOffset.animateTo(Offset.Zero)
176+
}
177+
178+
isRunning = false
179+
}
180+
)
181+
}
182+
.padding(dimensions.micro) // internal
183+
)
184+
}
185+
186+
@Composable
187+
private fun BoxScope.AlignmentTarget(
188+
isVisible: Boolean,
189+
alignment: Alignment,
190+
isTarget: Boolean,
191+
modifier: Modifier = Modifier
192+
) = AnimatedVisibility(
193+
visible = isVisible,
194+
enter = fadeIn(),
195+
exit = fadeOut(),
196+
modifier = modifier.align(alignment)
197+
) {
198+
Box(
199+
modifier = Modifier
200+
.background(
201+
color = colorScheme.primary.copy(
202+
alpha = 0.2f
203+
),
204+
shape = RoundedCornerShape(dimensions.tiny)
205+
).run {
206+
if (isTarget) {
207+
border(
208+
width = 1.dp,
209+
color = colorScheme.primary,
210+
shape = RoundedCornerShape(dimensions.tiny)
211+
)
212+
} else this
213+
}
214+
)
215+
}
216+
217+
data class MatchesInfos(
218+
val duration: Duration,
219+
val matches: Int
220+
) {
221+
companion object {
222+
fun create(
223+
duration: Duration = Duration.ZERO,
224+
matches: Int = 0
225+
): MatchesInfos? = when (platform) {
226+
is Platform.Android,
227+
is Platform.Desktop -> {
228+
MatchesInfos(
229+
duration = duration,
230+
matches = matches
231+
)
232+
}
233+
234+
Platform.Web -> null
235+
}
236+
}
237+
}

0 commit comments

Comments
 (0)