Last active
May 21, 2024 16:41
-
-
Save extremeheat/f51711e4d80effa8ea88a0f931d6c2d3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools"> | |
<uses-permission android:name="android.permission.INTERNET" /> | |
<application | |
android:allowBackup="true" | |
android:dataExtractionRules="@xml/data_extraction_rules" | |
android:fullBackupContent="@xml/backup_rules" | |
android:icon="@mipmap/ic_launcher" | |
android:label="@string/app_name" | |
android:roundIcon="@mipmap/ic_launcher_round" | |
android:supportsRtl="true" | |
android:theme="@style/Theme.NYCParks" | |
tools:targetApi="31"> | |
<activity | |
android:name=".MainActivity" | |
android:exported="true" | |
android:label="@string/app_name" | |
android:theme="@style/Theme.NYCParks"> | |
<intent-filter> | |
<action android:name="android.intent.action.MAIN" /> | |
<category android:name="android.intent.category.LAUNCHER" /> | |
</intent-filter> | |
</activity> | |
</application> | |
</manifest> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.nycparks | |
import android.content.Intent | |
import android.net.Uri | |
import android.os.Bundle | |
import androidx.activity.ComponentActivity | |
import androidx.activity.compose.setContent | |
import androidx.activity.enableEdgeToEdge | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.layout.Box | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Row | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.foundation.layout.padding | |
import androidx.compose.foundation.layout.width | |
import androidx.compose.foundation.lazy.LazyColumn | |
import androidx.compose.foundation.lazy.items | |
import androidx.compose.material3.Card | |
import androidx.compose.material3.CardDefaults | |
import androidx.compose.material3.ExperimentalMaterial3Api | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.material3.Scaffold | |
import androidx.compose.material3.Text | |
import androidx.compose.material3.TopAppBar | |
import androidx.compose.material3.TopAppBarDefaults | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.rememberCoroutineScope | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.rotate | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.graphics.graphicsLayer | |
import androidx.compose.ui.layout.layout | |
import androidx.compose.ui.platform.LocalContext | |
import androidx.compose.ui.platform.LocalUriHandler | |
import androidx.compose.ui.res.painterResource | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import androidx.navigation.NavController | |
import androidx.navigation.NavType | |
import androidx.navigation.compose.NavHost | |
import androidx.navigation.compose.composable | |
import androidx.navigation.compose.rememberNavController | |
import androidx.navigation.navArgument | |
import com.example.nycparks.ui.theme.NYCParksTheme | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.GlobalScope | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.runBlocking | |
import kotlinx.coroutines.withContext | |
import org.json.JSONArray | |
import java.net.HttpURLConnection | |
import java.net.URL | |
// define a Park data class | |
data class Park( | |
var boroCode: String, | |
val name: String, | |
val location: String, | |
val description: String, | |
val hours: String, | |
val image: Int?, | |
val url: String? = null | |
) | |
var allParks = listOf<Park>() | |
// do an async request to https://data.cityofnewyork.us/resource/enfh-gkve.json | |
// to get the list of parks in NYC | |
// then, parse the JSON response and display the list of parks in the app | |
fun parseParksData(jsonArray: JSONArray): List<Park> { | |
val parks = mutableListOf<Park>() | |
System.out.println("JSONA: " + jsonArray.length()); | |
for (i in 0 until jsonArray.length()) { | |
val parkJson = jsonArray.getJSONObject(i) | |
val borough = parkJson.getString("borough") | |
val name = parkJson.getString("signname") | |
val location = parkJson.getString("location") | |
val description = parkJson.getString("typecategory") | |
val parkUrl = try { | |
parkJson.getString("url") | |
} catch (e: Throwable) { | |
"" | |
} | |
parks.add(Park(borough, name, location, description, "", null, parkUrl)) | |
} | |
return parks | |
} | |
suspend fun fetchParksData(cb: (parks: List<Park>) -> Int): List<Park> { | |
val url = URL("https://data.cityofnewyork.us/resource/enfh-gkve.json") | |
return withContext(Dispatchers.IO) { | |
val connection = url.openConnection() as HttpURLConnection | |
try { | |
connection.inputStream.bufferedReader().use { reader -> | |
val response = reader.readText() | |
val jsonArray = JSONArray(response) | |
val par = parseParksData(jsonArray) | |
// val par = allParks; | |
cb(par) | |
par | |
} | |
} finally { | |
connection.disconnect() | |
} | |
} | |
} | |
class MainActivity : ComponentActivity() { | |
@OptIn(ExperimentalMaterial3Api::class) | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
enableEdgeToEdge() | |
setContent { | |
NYCParksTheme { | |
val coroutineScope = rememberCoroutineScope() | |
coroutineScope.launch { | |
fetchParksData { | |
allParks = it | |
System.out.println("Number of parks: " + allParks.size); | |
0 | |
} | |
} | |
val navController = rememberNavController() | |
Scaffold( | |
topBar = { | |
TopAppBar( | |
colors = TopAppBarDefaults.topAppBarColors( | |
containerColor = MaterialTheme.colorScheme.primaryContainer, | |
titleContentColor = MaterialTheme.colorScheme.primary, | |
), | |
title = { | |
Text("NYC Parks", fontWeight = FontWeight.Bold) | |
} | |
) | |
}, | |
modifier = Modifier.fillMaxSize() | |
) { innerPadding -> | |
NavHost( | |
navController = navController, | |
startDestination = "boroList", | |
modifier = Modifier.padding(innerPadding) | |
) { | |
composable("boroList") { | |
BoroCards(navController) | |
} | |
composable( | |
"parkList/{boro}", | |
arguments = listOf(navArgument("boro") { type = NavType.StringType }) | |
) { backStackEntry -> | |
val code = backStackEntry.arguments?.getString("boro") ?: ""; | |
ParkCards(allParks, code, navController) | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
fun Modifier.rotateVertically(clockwise: Boolean = true): Modifier { | |
val rotate = rotate(if (clockwise) 90f else -90f) | |
val adjustBounds = layout { measurable, constraints -> | |
val placeable = measurable.measure(constraints) | |
layout(placeable.height, placeable.width) { | |
placeable.place( | |
x = -(placeable.width / 2 - placeable.height / 2), | |
y = -(placeable.height / 2 - placeable.width / 2) | |
) | |
} | |
} | |
return rotate then adjustBounds | |
} | |
// define a BoroClass | |
data class Boro( | |
val name: String, | |
val code: String, | |
val image: Int | |
) | |
@Composable | |
fun BoroCards(navController: NavController) { | |
val boroughs = listOf( | |
Boro(name = "The Bronx", code = "X", image = R.drawable.thebronx), | |
Boro(name = "Brooklyn", code = "B", image = R.drawable.brooklyn), | |
Boro(name = "Manhattan", code = "M", image = R.drawable.manhattan), | |
Boro(name = "Queens", code = "Q", image = R.drawable.queens2), | |
Boro(name = "Staten Island", code = "S", image = R.drawable.statenisland) | |
) | |
LazyColumn() { | |
items(boroughs) { item -> | |
BoroCard(boro = item, navController) | |
} | |
} | |
} | |
@OptIn(ExperimentalMaterial3Api::class) | |
@Composable | |
fun BoroCard(boro: Boro, navController: NavController) { | |
Card( | |
// Set the background color of the card to light green | |
colors = CardDefaults.cardColors( | |
// light green | |
containerColor = Color(0xFFC5E1A5), | |
contentColor = Color.White //Card content color,e.g.text | |
), | |
modifier = Modifier | |
.padding(top = 20.dp, start = 10.dp, end = 10.dp), | |
onClick = { | |
// navController.navigate("parkList/${boro.code}") | |
// // fetch the parks data, then navigate to the park list screen | |
navController.navigate("parkList/${boro.code}") | |
} | |
// } | |
) { | |
Row { | |
Box( | |
modifier = Modifier | |
.align(Alignment.CenterVertically) | |
) { | |
Text( | |
text = boro.name, | |
fontWeight = FontWeight.Bold, | |
fontSize = 22.sp, | |
color = Color.Black, | |
modifier = Modifier | |
.rotateVertically(false) | |
.padding(10.dp) | |
) | |
} | |
// add in an image | |
Image( | |
painter = painterResource(id = boro.image), contentDescription = boro.name, | |
// stretch the image to fill the width of the card | |
modifier = Modifier | |
.fillMaxSize() | |
) | |
} | |
} | |
} | |
@Composable | |
fun ParkCards(parks: List<Park>, code: String, navController: NavController) { | |
// make a mock list of parks, like [{"name": "Astoria Park", "location": "", ...}, ...] | |
val mockParks = listOf( | |
Park( | |
boroCode = "Q", | |
name = "Astoria Park", | |
location = "Astoria Blvd. and 48 St. to Union Tp., Park Drive East.", | |
description = "This 59.96-acre park is located along the East River in Astoria, Queens. The park is home to the oldest and largest pool in New York City, the Astoria Pool. The pool is 330 feet long and 165 feet wide, and holds 1.1 million gallons of water. The park also features a track, tennis courts, basketball courts, playgrounds, and picnic areas.", | |
hours = "8 am - 12 pm", | |
image = null, | |
url = "https://www.nycgovparks.org/parks/astoria-park" | |
), | |
Park( | |
boroCode = "M", | |
name = "Central Park", | |
location = "Central Park, Manhattan", | |
description = "Central Park is a 843-acre park located in the heart of Manhattan. The park is home to many attractions, including the Central Park Zoo, the Central Park Conservatory Garden, the Central Park Carousel, and the Central Park Boathouse. The park also features many walking and biking paths, as well as playgrounds, picnic areas, and sports fields.", | |
hours = "6 am - 1 am", | |
image = null, | |
url = "https://www.centralparknyc.org/" | |
), | |
Park( | |
boroCode = "B", | |
name = "Prospect Park", | |
location = "Prospect Park, Brooklyn", | |
description = "Prospect Park is a 526-acre park located in the heart of Brooklyn. The park is home to many attractions, including the Prospect Park Zoo, the Prospect Park Bandshell, the Prospect Park Boathouse, and the Prospect Park Carousel. The park also features many walking and biking paths, as well as playgrounds, picnic areas, and sports fields.", | |
hours = "5 am - 1 am", | |
image = null, | |
url = "https://www.prospectpark.org/" | |
) | |
) | |
LazyColumn() { | |
items(parks) { item -> | |
// filter the parks by the code | |
if (item.boroCode.contains(code)) { | |
ParkCard(park = item) | |
} | |
} | |
} | |
} | |
@OptIn(ExperimentalMaterial3Api::class) | |
@Composable | |
fun ParkCard(park: Park) { | |
val uriCtx = LocalUriHandler.current | |
Card( | |
// Set the background color of the card to light green | |
colors = CardDefaults.cardColors( | |
// light green | |
containerColor = Color(0xFFC5E1A5), | |
contentColor = Color.White //Card content color,e.g.text | |
), | |
modifier = Modifier | |
.padding(top = 20.dp, start = 10.dp, end = 10.dp) | |
.height(160.dp) | |
.fillMaxSize(), | |
onClick = { | |
// open the park's URL in a browser | |
val url = park.url | |
if (url != null) { | |
uriCtx.openUri(url); | |
} | |
} | |
) { | |
Row( | |
modifier = Modifier.padding(10.dp) | |
) { | |
Image( | |
painter = painterResource(id = park.image ?: R.drawable.parks), | |
contentDescription = park.name, | |
// stretch the image to fill the width of the card | |
modifier = Modifier | |
.width(80.dp) | |
) | |
Column( | |
modifier = Modifier.padding(start = 10.dp) | |
) { | |
Text(text = park.name, color = Color.Black, fontSize = 22.sp, fontWeight = FontWeight.Bold) | |
Text(text = park.location, color = Color.Black, fontSize = 20.sp) | |
Text(text = "8 am - 12 pm", color = Color.Black, fontSize = 20.sp) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
plugins { | |
alias(libs.plugins.android.application) | |
alias(libs.plugins.jetbrains.kotlin.android) | |
} | |
android { | |
namespace = "com.example.nycparks" | |
compileSdk = 34 | |
defaultConfig { | |
applicationId = "com.example.nycparks" | |
minSdk = 24 | |
targetSdk = 34 | |
versionCode = 1 | |
versionName = "1.0" | |
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | |
vectorDrawables { | |
useSupportLibrary = true | |
} | |
} | |
buildTypes { | |
release { | |
isMinifyEnabled = false | |
proguardFiles( | |
getDefaultProguardFile("proguard-android-optimize.txt"), | |
"proguard-rules.pro" | |
) | |
} | |
} | |
compileOptions { | |
sourceCompatibility = JavaVersion.VERSION_1_8 | |
targetCompatibility = JavaVersion.VERSION_1_8 | |
} | |
kotlinOptions { | |
jvmTarget = "1.8" | |
} | |
buildFeatures { | |
compose = true | |
} | |
composeOptions { | |
kotlinCompilerExtensionVersion = "1.5.1" | |
} | |
packaging { | |
resources { | |
excludes += "/META-INF/{AL2.0,LGPL2.1}" | |
} | |
} | |
} | |
dependencies { | |
implementation(libs.androidx.core.ktx) | |
implementation(libs.androidx.lifecycle.runtime.ktx) | |
implementation(libs.androidx.activity.compose) | |
implementation(platform(libs.androidx.compose.bom)) | |
implementation(libs.androidx.ui) | |
implementation(libs.androidx.ui.graphics) | |
implementation(libs.androidx.ui.tooling.preview) | |
implementation(libs.androidx.material3) | |
implementation(libs.androidx.navigation.compose) | |
testImplementation(libs.junit) | |
androidTestImplementation(libs.androidx.junit) | |
androidTestImplementation(libs.androidx.espresso.core) | |
androidTestImplementation(platform(libs.androidx.compose.bom)) | |
androidTestImplementation(libs.androidx.ui.test.junit4) | |
debugImplementation(libs.androidx.ui.tooling) | |
debugImplementation(libs.androidx.ui.test.manifest) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment