Skip to content

Commit a5dbe6e

Browse files
author
sds100
committed
Merge branch 'feature/18-adaptable-layout' into develop
2 parents e245a0a + 56e80d0 commit a5dbe6e

8 files changed

Lines changed: 492 additions & 306 deletions

File tree

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ dependencies {
8686
implementation "com.google.accompanist:accompanist-insets:0.23.1"
8787
implementation "androidx.datastore:datastore-preferences:1.0.0"
8888
implementation "androidx.core:core-splashscreen:1.0.0-rc01"
89+
implementation "androidx.compose.material3:material3-window-size-class:1.0.0-alpha14"
8990

9091
//other
9192
implementation 'com.jakewharton.timber:timber:5.0.1'

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.viewModels
7+
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
8+
import androidx.compose.material3.windowsizeclass.WindowSizeClass
9+
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
710
import androidx.compose.runtime.Composable
811
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
912
import androidx.core.view.WindowCompat
@@ -17,14 +20,16 @@ import dagger.hilt.android.AndroidEntryPoint
1720
class MainActivity : ComponentActivity() {
1821
private val viewModel: MapViewModel by viewModels()
1922

23+
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
2024
override fun onCreate(savedInstanceState: Bundle?) {
2125
installSplashScreen()
2226
super.onCreate(savedInstanceState)
23-
27+
2428
WindowCompat.setDecorFitsSystemWindows(window, false)
2529

2630
setContent {
27-
MapcodeApp(viewModel)
31+
val windowSizeClass = calculateWindowSizeClass(this)
32+
MapcodeApp(viewModel, windowSizeClass)
2833
}
2934

3035
MapsInitializer.initialize(this)
@@ -38,12 +43,13 @@ class MainActivity : ComponentActivity() {
3843
}
3944

4045
@Composable
41-
fun MapcodeApp(viewModel: MapViewModel) {
46+
fun MapcodeApp(viewModel: MapViewModel, windowSizeClass: WindowSizeClass) {
4247
MapcodeTheme {
4348
val navController = rememberNavController()
4449
MapcodeNavHost(
4550
navController = navController,
46-
viewModel = viewModel
51+
viewModel = viewModel,
52+
windowSizeClass = windowSizeClass
4753
)
4854
}
4955
}

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

Lines changed: 19 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

@@ -16,15 +20,28 @@ import com.mapcode.map.MapViewModel
1620
fun MapcodeNavHost(
1721
modifier: Modifier = Modifier,
1822
navController: NavHostController,
19-
viewModel: MapViewModel
23+
viewModel: MapViewModel,
24+
windowSizeClass: WindowSizeClass
2025
) {
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+
2138
NavHost(
2239
navController = navController,
2340
startDestination = MapcodeDestination.Map.name,
2441
modifier = modifier
2542
) {
2643
composable(MapcodeDestination.Map.name) {
27-
MapScreen(viewModel)
44+
MapScreen(viewModel, layoutType = layoutType)
2845
}
2946
}
3047
}
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)