Skip to content

Commit 56e80d0

Browse files
author
sds100
committed
#18 feat: layout adapts to small displays
Signed-off-by: sds100 <developer.sds100@gmail.com>
1 parent d2aa885 commit 56e80d0

6 files changed

Lines changed: 432 additions & 289 deletions

File tree

app/src/main/java/com/mapcode/MainActivity.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.viewModels
77
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
8-
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
8+
import androidx.compose.material3.windowsizeclass.WindowSizeClass
99
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
1010
import androidx.compose.runtime.Composable
1111
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -15,7 +15,6 @@ import com.google.android.gms.maps.MapsInitializer
1515
import com.mapcode.map.MapViewModel
1616
import com.mapcode.theme.MapcodeTheme
1717
import dagger.hilt.android.AndroidEntryPoint
18-
import timber.log.Timber
1918

2019
@AndroidEntryPoint
2120
class MainActivity : ComponentActivity() {
@@ -29,9 +28,8 @@ class MainActivity : ComponentActivity() {
2928
WindowCompat.setDecorFitsSystemWindows(window, false)
3029

3130
setContent {
32-
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
33-
Timber.e(widthSizeClass.toString())
34-
MapcodeApp(viewModel, widthSizeClass)
31+
val windowSizeClass = calculateWindowSizeClass(this)
32+
MapcodeApp(viewModel, windowSizeClass)
3533
}
3634

3735
MapsInitializer.initialize(this)
@@ -45,13 +43,13 @@ class MainActivity : ComponentActivity() {
4543
}
4644

4745
@Composable
48-
fun MapcodeApp(viewModel: MapViewModel, widthSizeClass: WindowWidthSizeClass) {
46+
fun MapcodeApp(viewModel: MapViewModel, windowSizeClass: WindowSizeClass) {
4947
MapcodeTheme {
5048
val navController = rememberNavController()
5149
MapcodeNavHost(
5250
navController = navController,
5351
viewModel = viewModel,
54-
isExpandedScreen = widthSizeClass == WindowWidthSizeClass.Expanded
52+
windowSizeClass = windowSizeClass
5553
)
5654
}
5755
}

app/src/main/java/com/mapcode/MapcodeNavHost.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.mapcode
22

3+
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
4+
import androidx.compose.material3.windowsizeclass.WindowSizeClass
5+
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
36
import androidx.compose.runtime.Composable
47
import androidx.compose.ui.Modifier
58
import androidx.navigation.NavHostController
69
import androidx.navigation.compose.NavHost
710
import androidx.navigation.compose.composable
11+
import com.mapcode.map.LayoutType
812
import com.mapcode.map.MapScreen
913
import com.mapcode.map.MapViewModel
1014

@@ -17,15 +21,27 @@ fun MapcodeNavHost(
1721
modifier: Modifier = Modifier,
1822
navController: NavHostController,
1923
viewModel: MapViewModel,
20-
isExpandedScreen: Boolean
24+
windowSizeClass: WindowSizeClass
2125
) {
26+
val layoutType: LayoutType = when {
27+
windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
28+
&& windowSizeClass.widthSizeClass < WindowWidthSizeClass.Expanded -> LayoutType.VerticalInfoArea
29+
30+
windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
31+
&& windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded -> LayoutType.FloatingInfoArea
32+
33+
windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact -> LayoutType.HorizontalInfoArea
34+
windowSizeClass.widthSizeClass == WindowWidthSizeClass.Expanded -> LayoutType.FloatingInfoArea
35+
else -> LayoutType.HorizontalInfoArea
36+
}
37+
2238
NavHost(
2339
navController = navController,
2440
startDestination = MapcodeDestination.Map.name,
2541
modifier = modifier
2642
) {
2743
composable(MapcodeDestination.Map.name) {
28-
MapScreen(viewModel, isExpandedScreen = isExpandedScreen)
44+
MapScreen(viewModel, layoutType = layoutType)
2945
}
3046
}
3147
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.mapcode.map
2+
3+
import androidx.compose.foundation.layout.*
4+
import androidx.compose.material.MaterialTheme
5+
import androidx.compose.material.Text
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.res.stringResource
9+
import androidx.compose.ui.text.font.FontWeight
10+
import androidx.compose.ui.unit.dp
11+
import com.mapcode.R
12+
13+
@Composable
14+
fun AddressArea(
15+
modifier: Modifier = Modifier,
16+
address: String,
17+
onChange: (String) -> Unit,
18+
helper: AddressHelper,
19+
error: AddressError
20+
) {
21+
Column(modifier) {
22+
ClearableTextField(
23+
modifier = Modifier.fillMaxWidth(),
24+
text = address,
25+
onChange = onChange,
26+
label = stringResource(R.string.address_bar_label),
27+
clearButtonContentDescription = stringResource(R.string.clear_address_content_description)
28+
)
29+
30+
val extraTextHeight = 20.dp
31+
32+
//to prevent the layout jumping up and down fill with empty space if no helper or error
33+
if (helper == AddressHelper.None && error == AddressError.None) {
34+
Spacer(Modifier.height(extraTextHeight))
35+
} else {
36+
AddressHelper(
37+
Modifier
38+
.height(extraTextHeight)
39+
.padding(start = 4.dp), helper = helper
40+
)
41+
AddressError(
42+
Modifier
43+
.height(extraTextHeight)
44+
.padding(start = 4.dp), error = error
45+
)
46+
}
47+
}
48+
}
49+
50+
/**
51+
* Create the correct components for the helper message state.
52+
*/
53+
@Composable
54+
fun AddressHelper(modifier: Modifier = Modifier, helper: AddressHelper) {
55+
val helperMessage = when (helper) {
56+
AddressHelper.NoInternet -> stringResource(R.string.no_internet_error)
57+
AddressHelper.NoAddress -> stringResource(R.string.no_address_error)
58+
is AddressHelper.Location -> helper.location
59+
AddressHelper.None -> null
60+
}
61+
62+
if (helperMessage != null) {
63+
HelperText(modifier, message = helperMessage)
64+
}
65+
}
66+
67+
/**
68+
* Create the correct components for the error message state.
69+
*/
70+
@Composable
71+
fun AddressError(modifier: Modifier = Modifier, error: AddressError) {
72+
val errorMessage = when (error) {
73+
is AddressError.UnknownAddress -> stringResource(R.string.cant_find_address_error, error.addressQuery)
74+
AddressError.None -> null
75+
}
76+
77+
if (errorMessage != null) {
78+
ErrorText(modifier, message = errorMessage)
79+
}
80+
}
81+
82+
/**
83+
* This creates a Text styled as an error.
84+
*/
85+
@Composable
86+
fun ErrorText(modifier: Modifier = Modifier, message: String) {
87+
Text(
88+
modifier = modifier,
89+
text = message,
90+
color = MaterialTheme.colors.error,
91+
style = MaterialTheme.typography.body1,
92+
fontWeight = FontWeight.Bold
93+
)
94+
}
95+
96+
/**
97+
* This creates a Text styled for the helper messages.
98+
*/
99+
@Composable
100+
fun HelperText(modifier: Modifier = Modifier, message: String) {
101+
Text(modifier = modifier, text = message, style = MaterialTheme.typography.body1, fontWeight = FontWeight.Bold)
102+
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package com.mapcode.map
2+
3+
import androidx.compose.foundation.clickable
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.material.ScaffoldState
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.runtime.collectAsState
8+
import androidx.compose.runtime.getValue
9+
import androidx.compose.runtime.rememberCoroutineScope
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.res.stringResource
12+
import androidx.compose.ui.unit.dp
13+
import com.mapcode.R
14+
import kotlinx.coroutines.launch
15+
16+
@Composable
17+
fun VerticalInfoArea(
18+
modifier: Modifier = Modifier,
19+
state: UiState,
20+
onAddressChange: (String) -> Unit,
21+
onLatitudeChange: (String) -> Unit,
22+
onLongitudeChange: (String) -> Unit,
23+
onTerritoryClick: () -> Unit,
24+
onMapcodeClick: () -> Unit
25+
) {
26+
Column(modifier) {
27+
AddressArea(Modifier, state.addressUi.address, onAddressChange, state.addressUi.helper, state.addressUi.error)
28+
Spacer(Modifier.height(8.dp))
29+
TerritoryBox(
30+
modifier = Modifier
31+
.fillMaxWidth()
32+
.clickable { onTerritoryClick() },
33+
index = state.mapcodeUi.number,
34+
count = state.mapcodeUi.count,
35+
territoryName = state.mapcodeUi.territoryFullName
36+
)
37+
Spacer(Modifier.height(8.dp))
38+
MapcodeBox(
39+
Modifier
40+
.fillMaxWidth()
41+
.clickable { onMapcodeClick() },
42+
state.mapcodeUi.code,
43+
state.mapcodeUi.territoryShortName
44+
)
45+
Spacer(Modifier.height(8.dp))
46+
LatitudeTextBox(Modifier.fillMaxWidth(), state.latitude, onLatitudeChange)
47+
Spacer(Modifier.height(8.dp))
48+
LongitudeTextBox(Modifier.fillMaxWidth(), state.longitude, onLongitudeChange)
49+
}
50+
}
51+
52+
@Composable
53+
fun HorizontalInfoArea(
54+
modifier: Modifier = Modifier,
55+
state: UiState,
56+
onAddressChange: (String) -> Unit,
57+
onLatitudeChange: (String) -> Unit,
58+
onLongitudeChange: (String) -> Unit,
59+
onTerritoryClick: () -> Unit,
60+
onMapcodeClick: () -> Unit
61+
) {
62+
Column(modifier) {
63+
AddressArea(
64+
Modifier.fillMaxWidth(),
65+
state.addressUi.address,
66+
onAddressChange,
67+
state.addressUi.helper,
68+
state.addressUi.error
69+
)
70+
Row(Modifier.padding(top = 8.dp)) {
71+
TerritoryBox(
72+
modifier = Modifier
73+
.weight(0.5f)
74+
.padding(end = 8.dp)
75+
.clickable { onTerritoryClick() },
76+
index = state.mapcodeUi.number,
77+
count = state.mapcodeUi.count,
78+
territoryName = state.mapcodeUi.territoryFullName
79+
)
80+
MapcodeBox(
81+
Modifier
82+
.weight(0.5f)
83+
.padding(start = 8.dp)
84+
.clickable { onMapcodeClick() },
85+
state.mapcodeUi.code,
86+
state.mapcodeUi.territoryShortName
87+
)
88+
}
89+
90+
Row(Modifier.padding(top = 8.dp)) {
91+
LatitudeTextBox(
92+
Modifier
93+
.weight(0.5f)
94+
.padding(end = 8.dp)
95+
.fillMaxWidth(), state.latitude, onLatitudeChange
96+
)
97+
LongitudeTextBox(
98+
Modifier
99+
.weight(0.5f)
100+
.padding(start = 8.dp), state.longitude, onLongitudeChange
101+
)
102+
}
103+
}
104+
}
105+
106+
@Composable
107+
fun InfoArea(
108+
modifier: Modifier = Modifier,
109+
state: UiState,
110+
onAddressChange: (String) -> Unit = {},
111+
onLatitudeChange: (String) -> Unit = {},
112+
onLongitudeChange: (String) -> Unit = {},
113+
onTerritoryClick: () -> Unit = {},
114+
onMapcodeClick: () -> Unit = {},
115+
isVerticalLayout: Boolean
116+
) {
117+
if (isVerticalLayout) {
118+
VerticalInfoArea(
119+
modifier,
120+
state,
121+
onAddressChange,
122+
onLatitudeChange,
123+
onLongitudeChange,
124+
onTerritoryClick,
125+
onMapcodeClick
126+
)
127+
} else {
128+
HorizontalInfoArea(
129+
modifier,
130+
state,
131+
onAddressChange,
132+
onLatitudeChange,
133+
onLongitudeChange,
134+
onTerritoryClick,
135+
onMapcodeClick
136+
)
137+
}
138+
}
139+
140+
@Composable
141+
fun InfoArea(
142+
modifier: Modifier,
143+
viewModel: MapViewModel,
144+
scaffoldState: ScaffoldState,
145+
isVerticalLayout: Boolean
146+
) {
147+
val uiState by viewModel.uiState.collectAsState()
148+
val scope = rememberCoroutineScope()
149+
val copiedMessageStr = stringResource(R.string.copied_to_clipboard_snackbar_text)
150+
151+
InfoArea(
152+
modifier,
153+
uiState,
154+
onMapcodeClick = {
155+
val copied = viewModel.copyMapcode()
156+
if (copied) {
157+
scope.launch {
158+
//dismiss current snack bar so they aren't queued up
159+
scaffoldState.snackbarHostState.currentSnackbarData?.dismiss()
160+
scaffoldState.snackbarHostState.showSnackbar(copiedMessageStr)
161+
}
162+
}
163+
},
164+
onAddressChange = { viewModel.queryAddress(it) },
165+
onTerritoryClick = { viewModel.onTerritoryClick() },
166+
onLatitudeChange = { viewModel.queryLatitude(it) },
167+
onLongitudeChange = { viewModel.queryLongitude(it) },
168+
isVerticalLayout = isVerticalLayout
169+
)
170+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.mapcode.map
2+
3+
enum class LayoutType {
4+
VerticalInfoArea, HorizontalInfoArea, FloatingInfoArea
5+
}

0 commit comments

Comments
 (0)