Skip to content

Instantly share code, notes, and snippets.

@extremeheat
Last active May 21, 2024 16:41
Show Gist options
  • Save extremeheat/f51711e4d80effa8ea88a0f931d6c2d3 to your computer and use it in GitHub Desktop.
Save extremeheat/f51711e4d80effa8ea88a0f931d6c2d3 to your computer and use it in GitHub Desktop.
<?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>
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)
}
}
}
}
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