Skip to content

Commit ba0cf28

Browse files
authored
Merge pull request #644 from flow-mn/develop
release :)
2 parents 9bb97b6 + 6e699ca commit ba0cf28

File tree

90 files changed

+3776
-626
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+3776
-626
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Next
44

5+
### 0.18.0
6+
7+
* Now you can scan receipts with Eny (beta)
8+
9+
## 0.17.0
10+
511
### New features
612

713
* Now you can configure transaction entry flows

android/app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ plugins {
1111
android {
1212
namespace = "mn.flow.flow"
1313
compileSdk = 36
14-
ndkVersion = "28.0.13004108"
14+
ndkVersion = "28.2.13676358"
1515

1616
buildFeatures {
1717
compose = true

android/app/src/main/AndroidManifest.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,26 @@
8686
android:name="android.appwidget.provider"
8787
android:resource="@xml/two_entry_widget_info" />
8888
</receiver>
89+
<receiver
90+
android:name=".glance.TwoEntryLastReceiver"
91+
android:exported="true">
92+
<intent-filter>
93+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
94+
</intent-filter>
95+
<meta-data
96+
android:name="android.appwidget.provider"
97+
android:resource="@xml/two_entry_last_widget_info" />
98+
</receiver>
99+
<receiver
100+
android:name=".glance.FourEntryReceiver"
101+
android:exported="true">
102+
<intent-filter>
103+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
104+
</intent-filter>
105+
<meta-data
106+
android:name="android.appwidget.provider"
107+
android:resource="@xml/four_entry_widget_info" />
108+
</receiver>
89109
<provider
90110
android:name="androidx.core.content.FileProvider"
91111
android:authorities="${applicationId}.provider"
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package mn.flow.flow
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.content.SharedPreferences
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.unit.Dp
8+
import androidx.compose.ui.unit.DpSize
9+
import androidx.compose.ui.unit.dp
10+
import androidx.core.net.toUri
11+
import androidx.glance.ColorFilter
12+
import androidx.glance.GlanceModifier
13+
import androidx.glance.GlanceTheme
14+
import androidx.glance.Image
15+
import androidx.glance.ImageProvider
16+
import androidx.glance.action.clickable
17+
import androidx.glance.appwidget.action.actionStartActivity
18+
import androidx.glance.appwidget.cornerRadius
19+
import androidx.glance.background
20+
import androidx.glance.layout.Alignment
21+
import androidx.glance.layout.Box
22+
import androidx.glance.layout.ContentScale
23+
import androidx.glance.layout.height
24+
import androidx.glance.layout.padding
25+
import androidx.glance.layout.width
26+
27+
abstract class FlowWidgetUtils {
28+
companion object {
29+
val defaultButtonOrder = listOf(
30+
"eny",
31+
"transfer",
32+
"income",
33+
"expense",
34+
)
35+
36+
val imageMapping = mapOf(
37+
"eny" to R.drawable.camera,
38+
"transfer" to R.drawable.transfer,
39+
"income" to R.drawable.income,
40+
"expense" to R.drawable.expense,
41+
)
42+
43+
fun getButtonOrder(prefs: SharedPreferences?): List<String> {
44+
var buttonOrder =
45+
prefs?.getString("buttonOrder", null)?.split(",")
46+
?: defaultButtonOrder
47+
48+
if (buttonOrder.size < 2) {
49+
buttonOrder = defaultButtonOrder
50+
}
51+
52+
return buttonOrder
53+
}
54+
55+
56+
@Composable
57+
fun EntryButton(context: Context, operation: String, size: DpSize, buttonSize: Dp, padEnd: Boolean, pill: Boolean = false) {
58+
val iconSize = buttonSize * 2 / 3
59+
val buttonWidth = if (pill) (buttonSize * 2 + 8.dp) else buttonSize
60+
61+
Box(modifier = GlanceModifier.padding(end = if(padEnd) 8.dp else 0.dp)) {
62+
Box(
63+
modifier = GlanceModifier
64+
.background(GlanceTheme.colors.primary)
65+
.width(buttonWidth)
66+
.height(buttonSize)
67+
.cornerRadius(999.dp)
68+
.clickable(
69+
onClick = actionStartActivity(
70+
Intent(Intent.ACTION_VIEW, "flow-mn:///transaction/new?type=${operation}".toUri()).apply {
71+
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
72+
}
73+
)
74+
),
75+
contentAlignment = Alignment.Center
76+
) {
77+
Image(
78+
ImageProvider(FlowWidgetUtils.imageMapping[operation.lowercase()] ?: R.drawable.flow),
79+
modifier = GlanceModifier.height(iconSize).width(iconSize),
80+
contentDescription = if (operation.lowercase() == "eny") "Scan receipt with Eny" else "New $operation",
81+
colorFilter = ColorFilter.tint(GlanceTheme.colors.primaryContainer),
82+
contentScale = ContentScale.Fit
83+
)
84+
}
85+
}
86+
}
87+
}
88+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package mn.flow.flow.glance
2+
3+
import HomeWidgetGlanceState
4+
import HomeWidgetGlanceStateDefinition
5+
import android.content.Context
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.unit.dp
8+
import androidx.compose.ui.unit.min
9+
import androidx.glance.GlanceId
10+
import androidx.glance.GlanceModifier
11+
import androidx.glance.GlanceTheme
12+
import androidx.glance.LocalSize
13+
import androidx.glance.appwidget.GlanceAppWidget
14+
import androidx.glance.appwidget.SizeMode
15+
import androidx.glance.appwidget.provideContent
16+
import androidx.glance.background
17+
import androidx.glance.currentState
18+
import androidx.glance.layout.Alignment
19+
import androidx.glance.layout.Box
20+
import androidx.glance.layout.Row
21+
import androidx.glance.layout.Column
22+
import androidx.glance.layout.fillMaxSize
23+
import androidx.glance.layout.padding
24+
import androidx.glance.preview.ExperimentalGlancePreviewApi
25+
import androidx.glance.preview.Preview
26+
import androidx.glance.state.GlanceStateDefinition
27+
import mn.flow.flow.FlowWidgetUtils
28+
29+
class FourEntry : GlanceAppWidget() {
30+
override val sizeMode = SizeMode.Exact
31+
32+
override val stateDefinition: GlanceStateDefinition<*>
33+
get() = HomeWidgetGlanceStateDefinition()
34+
35+
override suspend fun provideGlance(context: Context, id: GlanceId) {
36+
provideContent {
37+
GlanceTheme {
38+
Content(context, currentState())
39+
}
40+
}
41+
}
42+
}
43+
@OptIn(ExperimentalGlancePreviewApi::class)
44+
@Composable
45+
@Preview(widthDp = 100, heightDp = 50)
46+
private fun Content(context: Context, currentState: HomeWidgetGlanceState) {
47+
val buttonOrder = FlowWidgetUtils.getButtonOrder(currentState.preferences)
48+
49+
val size = LocalSize.current
50+
val buttonSize = min((size.width / 2 - 24.dp), (size.height - 16.dp))
51+
52+
val firstRowItemCount = if(buttonOrder.size > 3) 2 else 1
53+
54+
val firstRow = buttonOrder.subList(0, firstRowItemCount)
55+
val secondRow = buttonOrder.subList(firstRowItemCount, buttonOrder.size)
56+
57+
Box(
58+
modifier = GlanceModifier.background(GlanceTheme.colors.widgetBackground).fillMaxSize(),
59+
contentAlignment = Alignment.Center
60+
) {
61+
Column() {
62+
Row(
63+
modifier = GlanceModifier.padding(8.dp, bottom = 4.dp),
64+
verticalAlignment = Alignment.CenterVertically,
65+
) {
66+
firstRow.forEachIndexed { index, operation ->
67+
FlowWidgetUtils.EntryButton(context, operation = operation, size = size, buttonSize = buttonSize, padEnd = index == 0 && firstRow.size > 1, pill = firstRow.size == 1)
68+
}
69+
}
70+
Row(
71+
modifier = GlanceModifier.padding(8.dp, top = 4.dp),
72+
verticalAlignment = Alignment.CenterVertically,
73+
) {
74+
secondRow.forEachIndexed { index, operation ->
75+
FlowWidgetUtils.EntryButton(context, operation = operation, size = size, buttonSize = buttonSize, padEnd = index == 0)
76+
}
77+
}
78+
}
79+
}
80+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package mn.flow.flow.glance
2+
3+
import androidx.glance.appwidget.GlanceAppWidget
4+
import androidx.glance.appwidget.GlanceAppWidgetReceiver
5+
import mn.flow.flow.glance.FourEntry
6+
7+
class FourEntryReceiver : GlanceAppWidgetReceiver() {
8+
override val glanceAppWidget: GlanceAppWidget = FourEntry()
9+
}

android/app/src/main/kotlin/mn/flow/flow/glance/TwoEntry.kt

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,27 @@ package mn.flow.flow.glance
33
import HomeWidgetGlanceState
44
import HomeWidgetGlanceStateDefinition
55
import android.content.Context
6-
import android.content.Intent
7-
import android.content.SharedPreferences
86
import androidx.compose.runtime.Composable
97
import androidx.compose.ui.unit.dp
108
import androidx.compose.ui.unit.min
11-
import androidx.core.net.toUri
12-
import androidx.glance.ColorFilter
139
import androidx.glance.GlanceId
1410
import androidx.glance.GlanceModifier
1511
import androidx.glance.GlanceTheme
16-
import androidx.glance.Image
17-
import androidx.glance.ImageProvider
1812
import androidx.glance.LocalSize
19-
import androidx.glance.action.clickable
2013
import androidx.glance.appwidget.GlanceAppWidget
2114
import androidx.glance.appwidget.SizeMode
22-
import androidx.glance.appwidget.action.actionStartActivity
23-
import androidx.glance.appwidget.cornerRadius
2415
import androidx.glance.appwidget.provideContent
2516
import androidx.glance.background
2617
import androidx.glance.currentState
2718
import androidx.glance.layout.Alignment
2819
import androidx.glance.layout.Box
29-
import androidx.glance.layout.ContentScale
3020
import androidx.glance.layout.Row
3121
import androidx.glance.layout.fillMaxSize
32-
import androidx.glance.layout.height
3322
import androidx.glance.layout.padding
34-
import androidx.glance.layout.width
3523
import androidx.glance.preview.ExperimentalGlancePreviewApi
3624
import androidx.glance.preview.Preview
3725
import androidx.glance.state.GlanceStateDefinition
38-
import mn.flow.flow.R
39-
import java.util.Locale.getDefault
26+
import mn.flow.flow.FlowWidgetUtils
4027

4128
class TwoEntry : GlanceAppWidget() {
4229
override val sizeMode = SizeMode.Exact
@@ -53,32 +40,14 @@ class TwoEntry : GlanceAppWidget() {
5340
}
5441
}
5542

56-
private val defaultOrder = listOf(
57-
"income",
58-
"expense",
59-
)
60-
61-
private fun getButtonOrder(prefs: SharedPreferences?): List<String> {
62-
var buttonOrder =
63-
prefs?.getString("buttonOrder", null)?.split(",")
64-
?: defaultOrder
65-
66-
if (!buttonOrder.containsAll(defaultOrder)) {
67-
buttonOrder = defaultOrder
68-
}
69-
70-
return buttonOrder.filter { item -> item != "transfer" }
71-
}
72-
7343
@OptIn(ExperimentalGlancePreviewApi::class)
7444
@Composable
7545
@Preview(widthDp = 100, heightDp = 50)
7646
private fun Content(context: Context, currentState: HomeWidgetGlanceState) {
77-
val buttonOrder = getButtonOrder(currentState?.preferences)
47+
val buttonOrder = FlowWidgetUtils.getButtonOrder(currentState.preferences).subList(0, 2)
7848

7949
val size = LocalSize.current
8050
val buttonSize = min((size.width / 2 - 24.dp), (size.height - 16.dp))
81-
val iconSize = buttonSize * 2 / 3
8251

8352
Box(
8453
modifier = GlanceModifier.background(GlanceTheme.colors.widgetBackground).fillMaxSize(),
@@ -89,44 +58,7 @@ private fun Content(context: Context, currentState: HomeWidgetGlanceState) {
8958
verticalAlignment = Alignment.CenterVertically,
9059
) {
9160
buttonOrder.forEachIndexed { index, operation ->
92-
Box(modifier = GlanceModifier.padding(end = if (index == 0) 8.dp else 0.dp)) {
93-
Box(
94-
modifier = GlanceModifier
95-
.background(GlanceTheme.colors.primary)
96-
.width(buttonSize)
97-
.height(buttonSize)
98-
.cornerRadius(999.dp)
99-
.clickable(
100-
onClick = actionStartActivity(
101-
Intent(Intent.ACTION_VIEW, "flow-mn:///transaction/new?type=${operation}".toUri()).apply {
102-
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
103-
}
104-
)
105-
),
106-
contentAlignment = Alignment.Center
107-
) {
108-
Image(
109-
ImageProvider(if (operation.lowercase(getDefault()) == "expense") R.drawable.expense else R.drawable.income),
110-
modifier = GlanceModifier.height(iconSize).width(iconSize),
111-
contentDescription = "New $operation",
112-
colorFilter = ColorFilter.tint(GlanceTheme.colors.primaryContainer),
113-
contentScale = ContentScale.Fit
114-
)
115-
}
116-
// CircleIconButton(
117-
// ImageProvider(if (operation.lowercase(getDefault()) == "expense") R.drawable.expense else R.drawable.income),
118-
// backgroundColor = GlanceTheme.colors.primary,
119-
// contentColor = GlanceTheme.colors.widgetBackground,
120-
// contentDescription = "New $operation",
121-
// onClick =
122-
// actionStartActivity(
123-
// Intent(
124-
// Intent.ACTION_VIEW,
125-
// "flow-mn:///transaction/new?type=${operation}".toUri()
126-
// )
127-
// )
128-
// )
129-
}
61+
FlowWidgetUtils.EntryButton(context, operation = operation, size = size, buttonSize = buttonSize, padEnd = index == 0)
13062
}
13163
}
13264
}

0 commit comments

Comments
 (0)