Skip to content

Commit beb9859

Browse files
committed
grow withered tree
1 parent b21f540 commit beb9859

6 files changed

Lines changed: 330 additions & 38 deletions

File tree

composeApp/src/commonMain/kotlin/neth/iecal/trease/ui/bottomsheet/TreeBottomSheet.kt renamed to composeApp/src/commonMain/kotlin/neth/iecal/trease/ui/bottomsheet/SelectTreeBottomSheet.kt

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
package neth.iecal.trease.ui.bottomsheet
22

33
import androidx.compose.animation.*
4-
import androidx.compose.animation.core.tween
54
import androidx.compose.foundation.ExperimentalFoundationApi
65
import androidx.compose.foundation.background
76
import androidx.compose.foundation.combinedClickable
87
import androidx.compose.foundation.layout.*
98
import androidx.compose.foundation.lazy.LazyRow
109
import androidx.compose.foundation.lazy.grid.*
11-
import androidx.compose.foundation.shape.CircleShape
1210
import androidx.compose.foundation.shape.RoundedCornerShape
1311
import androidx.compose.material3.*
1412
import androidx.compose.runtime.*
1513
import androidx.compose.ui.Alignment
1614
import androidx.compose.ui.ExperimentalComposeUiApi
1715
import androidx.compose.ui.Modifier
1816
import androidx.compose.ui.draw.clip
19-
import androidx.compose.ui.graphics.Brush
20-
import androidx.compose.ui.graphics.Color
2117
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
2218
import androidx.compose.ui.layout.ContentScale
2319
import androidx.compose.ui.platform.LocalHapticFeedback
@@ -36,23 +32,21 @@ import neth.iecal.trease.utils.CacheManager
3632

3733
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
3834
@Composable
39-
fun TreeBottomSheet(
35+
fun GrowTreeBottomSheet(
4036
onDismiss: () -> Unit,
41-
onSelected: (TreeData) -> Unit
37+
onSelected: (TreeData) -> Unit,
38+
onShowWitheredTrees:()->Unit,
4239
) {
4340
// State
4441
var uiState by remember { mutableStateOf<TreeUiState>(TreeUiState.Loading) }
4542
var selectedTree by remember { mutableStateOf<TreeData?>(null) }
4643

47-
// Data Loading Logic
4844
LaunchedEffect(Unit) {
4945
val cacheManager = CacheManager()
50-
// Optimistic load from cache
5146
cacheManager.readFile("tree.json")?.let {
5247
uiState = TreeUiState.Success(Json.decodeFromString(it))
5348
}
5449

55-
// Fetch fresh data
5650
try {
5751
val trees = TreeRepository.fetchTrees()
5852
uiState = TreeUiState.Success(trees)
@@ -70,7 +64,6 @@ fun TreeBottomSheet(
7064
containerColor = MaterialTheme.colorScheme.surface,
7165
tonalElevation = 0.dp // Cleaner flat look
7266
) {
73-
// Smart Transition between List and Details
7467
AnimatedContent(
7568
targetState = selectedTree,
7669
transitionSpec = {
@@ -92,7 +85,8 @@ fun TreeBottomSheet(
9285
TreeGridView(
9386
uiState = uiState,
9487
onTreeSelected = { onSelected(it) },
95-
onTreeLongPress = { selectedTree = it }
88+
onTreeLongPress = { selectedTree = it },
89+
onShowWitheredTrees
9690
)
9791
}
9892
}
@@ -104,7 +98,8 @@ fun TreeBottomSheet(
10498
private fun TreeGridView(
10599
uiState: TreeUiState,
106100
onTreeSelected: (TreeData) -> Unit,
107-
onTreeLongPress: (TreeData) -> Unit
101+
onTreeLongPress: (TreeData) -> Unit,
102+
onShowWitheredTrees: () -> Unit
108103
) {
109104
Column(
110105
modifier = Modifier
@@ -123,6 +118,14 @@ private fun TreeGridView(
123118
style = MaterialTheme.typography.bodySmall,
124119
)
125120

121+
122+
Spacer(modifier = Modifier.height(8.dp))
123+
124+
SuggestionChip(
125+
onClick = {onShowWitheredTrees() },
126+
label = { Text("Plant a Withered Tree") },
127+
)
128+
126129
Spacer(modifier = Modifier.height(16.dp))
127130

128131
when (uiState) {
@@ -172,7 +175,7 @@ private fun TreeDetailView(
172175
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp),) {
173176
// Main Preview Image
174177
AsyncImage(
175-
model = "${Constants.cdn}/images/${tree.id}_${tree.variants - 1}.png",
178+
model = "${Constants.cdn}/images/${tree.id}_${tree.variants - 1}_grid.png",
176179
contentDescription = null,
177180
contentScale = ContentScale.Fit,
178181
modifier = Modifier.size(200.dp)
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package neth.iecal.trease.ui.bottomsheet
2+
3+
4+
import androidx.compose.animation.*
5+
import androidx.compose.foundation.ExperimentalFoundationApi
6+
import androidx.compose.foundation.combinedClickable
7+
import androidx.compose.foundation.layout.*
8+
import androidx.compose.foundation.lazy.grid.*
9+
import androidx.compose.foundation.shape.RoundedCornerShape
10+
import androidx.compose.material3.*
11+
import androidx.compose.runtime.*
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.ExperimentalComposeUiApi
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.draw.clip
16+
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
17+
import androidx.compose.ui.layout.ContentScale
18+
import androidx.compose.ui.platform.LocalHapticFeedback
19+
import androidx.compose.ui.text.font.FontWeight
20+
import androidx.compose.ui.text.style.TextAlign
21+
import androidx.compose.ui.text.style.TextOverflow
22+
import androidx.compose.ui.unit.dp
23+
import coil3.compose.AsyncImage
24+
import neth.iecal.trease.Constants
25+
import neth.iecal.trease.models.FocusStats
26+
import neth.iecal.trease.utils.TreeStatsLodger
27+
import neth.iecal.trease.utils.getDate
28+
import neth.iecal.trease.viewmodels.HomeScreenViewModel
29+
30+
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
31+
@Composable
32+
fun WitheredTreeSheet(
33+
onWitheredTreeSelected: (FocusStats) -> Unit,
34+
onDismissRequest: () -> Unit,
35+
) {
36+
var trees by remember { mutableStateOf<List<FocusStats>?>(null) }
37+
var selectedTree by remember { mutableStateOf<FocusStats?>(null) }
38+
39+
LaunchedEffect(Unit) {
40+
val treeStatsLodger = TreeStatsLodger()
41+
trees = treeStatsLodger.getCache().filter { it.isFailed }
42+
}
43+
ModalBottomSheet(
44+
onDismissRequest = onDismissRequest,
45+
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
46+
containerColor = MaterialTheme.colorScheme.surface,
47+
) {
48+
AnimatedContent(
49+
targetState = selectedTree,
50+
transitionSpec = {
51+
if (targetState != null) {
52+
slideInHorizontally { it } + fadeIn() togetherWith slideOutHorizontally { -it / 2 } + fadeOut()
53+
} else {
54+
slideInHorizontally { -it / 2 } + fadeIn() togetherWith slideOutHorizontally { it } + fadeOut()
55+
}.using(SizeTransform(clip = false))
56+
},
57+
label = "TreeSheetContent"
58+
) { tree ->
59+
if (tree != null) {
60+
WitheredTreeDetailView(
61+
tree = tree,
62+
onBack = { selectedTree = null },
63+
onSelect = {
64+
onWitheredTreeSelected(selectedTree!!)
65+
onDismissRequest()
66+
}
67+
)
68+
} else {
69+
WitheredTreeGridView(
70+
treeList = trees ?: emptyList(),
71+
onTreeSelected = {
72+
onWitheredTreeSelected(it)
73+
onDismissRequest()
74+
},
75+
onTreeLongPress = { selectedTree = it }
76+
)
77+
}
78+
}
79+
}
80+
}
81+
82+
83+
@Composable
84+
private fun WitheredTreeGridView(
85+
treeList: List<FocusStats>,
86+
onTreeSelected: (FocusStats) -> Unit,
87+
onTreeLongPress: (FocusStats) -> Unit
88+
) {
89+
Column(
90+
modifier = Modifier
91+
.fillMaxWidth()
92+
.heightIn(min = 400.dp)
93+
.padding(horizontal = 24.dp),
94+
horizontalAlignment = Alignment.CenterHorizontally
95+
) {
96+
Text(
97+
text = "Select a Withered Tree",
98+
style = MaterialTheme.typography.headlineSmall,
99+
fontWeight = FontWeight.Bold
100+
)
101+
Text(
102+
text = "Tap to plant, hold to view details",
103+
style = MaterialTheme.typography.bodySmall,
104+
)
105+
106+
Spacer(modifier = Modifier.height(16.dp))
107+
108+
LazyVerticalGrid(
109+
columns = GridCells.Adaptive(minSize = 100.dp),
110+
contentPadding = PaddingValues(bottom = 24.dp),
111+
verticalArrangement = Arrangement.spacedBy(16.dp),
112+
horizontalArrangement = Arrangement.spacedBy(16.dp)
113+
) {
114+
items(
115+
items = treeList,
116+
key = { it.id }
117+
) { tree ->
118+
WitheredTreeGridItem(tree, onTreeSelected, onTreeLongPress)
119+
}
120+
}
121+
}
122+
}
123+
124+
@Composable
125+
private fun WitheredTreeDetailView(
126+
tree: FocusStats,
127+
onBack: () -> Unit,
128+
onSelect: () -> Unit
129+
) {
130+
Column(
131+
modifier = Modifier
132+
.fillMaxWidth()
133+
.padding(horizontal = 24.dp)
134+
.padding(bottom = 32.dp)
135+
) {
136+
SuggestionChip(
137+
onClick = {onBack() },
138+
label = { Text("Go Back") },
139+
)
140+
141+
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp),) {
142+
AsyncImage(
143+
model = "${Constants.cdn}/images/${tree.treeId}_0_grid.png",
144+
contentDescription = null,
145+
contentScale = ContentScale.Fit,
146+
modifier = Modifier.size(200.dp)
147+
)
148+
149+
Column(horizontalAlignment = Alignment.CenterHorizontally,
150+
verticalArrangement = Arrangement.Center,) {
151+
Text("${tree.duration / 60} mins", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold)
152+
Text(
153+
text = "Failed on: ${tree.completedOn.getDate()}",
154+
style = MaterialTheme.typography.labelLarge,
155+
)
156+
}
157+
}
158+
159+
Button(
160+
onClick = onSelect,
161+
modifier = Modifier.fillMaxWidth().height(50.dp),
162+
shape = RoundedCornerShape(12.dp)
163+
) {
164+
Text("Grow This Tree")
165+
}
166+
}
167+
}
168+
169+
@OptIn(ExperimentalFoundationApi::class)
170+
@Composable
171+
private fun WitheredTreeGridItem(
172+
tree: FocusStats,
173+
onClick: (FocusStats) -> Unit,
174+
onLongClick: (FocusStats) -> Unit
175+
) {
176+
val haptic = LocalHapticFeedback.current
177+
178+
Column(
179+
horizontalAlignment = Alignment.CenterHorizontally,
180+
modifier = Modifier
181+
.clip(RoundedCornerShape(16.dp))
182+
.combinedClickable(
183+
onClick = { onClick(tree) },
184+
onLongClick = {
185+
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
186+
onLongClick(tree)
187+
}
188+
)
189+
.padding(8.dp)
190+
) {
191+
// Image Container
192+
Box(
193+
modifier = Modifier
194+
.size(90.dp)
195+
.padding(8.dp),
196+
contentAlignment = Alignment.Center
197+
) {
198+
AsyncImage(
199+
model = "${Constants.cdn}/images/${tree.treeId}_0.png",
200+
contentDescription = tree.treeId,
201+
modifier = Modifier.fillMaxSize(),
202+
contentScale = ContentScale.Fit
203+
)
204+
}
205+
206+
Spacer(Modifier.height(8.dp))
207+
208+
Text(
209+
text = "${tree.duration / 60} mins",
210+
style = MaterialTheme.typography.labelMedium,
211+
textAlign = TextAlign.Center,
212+
maxLines = 1,
213+
overflow = TextOverflow.Ellipsis
214+
)
215+
}
216+
}
217+

composeApp/src/commonMain/kotlin/neth/iecal/trease/ui/screens/FullScreenGarden.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private fun GlassHudWidget(
180180
}
181181

182182
@Composable
183-
fun ZoomableMapLayer(content: @Composable () -> Unit) {
183+
private fun ZoomableMapLayer(content: @Composable () -> Unit) {
184184
var scale by remember { mutableFloatStateOf(1f) }
185185
var offset by remember { mutableStateOf(Offset.Zero) }
186186

@@ -207,7 +207,7 @@ fun ZoomableMapLayer(content: @Composable () -> Unit) {
207207
}
208208

209209
@Composable
210-
fun MiniMonthSelector(date: LocalDate, onMonthChange: (LocalDate) -> Unit) {
210+
private fun MiniMonthSelector(date: LocalDate, onMonthChange: (LocalDate) -> Unit) {
211211
Row(
212212
verticalAlignment = Alignment.CenterVertically,
213213
horizontalArrangement = Arrangement.spacedBy(4.dp)
@@ -235,7 +235,7 @@ fun MiniMonthSelector(date: LocalDate, onMonthChange: (LocalDate) -> Unit) {
235235
}
236236

237237
@Composable
238-
fun MiniResourceCounter(focus: Int, streak: Int) {
238+
private fun MiniResourceCounter(focus: Int, streak: Int) {
239239
Row(horizontalArrangement = Arrangement.spacedBy(16.dp,Alignment.CenterHorizontally)) {
240240
Column(horizontalAlignment = Alignment.End) {
241241
Text("$focus", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold))

0 commit comments

Comments
 (0)