From 9d87bf4575822c670910d2367124268e129a8d3b Mon Sep 17 00:00:00 2001 From: Cole Landers Date: Thu, 21 Dec 2023 08:24:10 -0600 Subject: [PATCH] init commit --- .gitignore | 38 ++++++ .idea/.gitignore | 3 + .idea/encodings.xml | 7 + .idea/inspectionProfiles/Project_Default.xml | 8 ++ .idea/misc.xml | 15 +++ .idea/uiDesigner.xml | 124 ++++++++++++++++++ .idea/vcs.xml | 6 + pom.xml | 49 +++++++ .../java/dev/clanders/weatherapi/Api.java | 27 ++++ .../weatherapi/request/SearchLocation.java | 6 + .../request/SearchLocationBuilder.java | 12 ++ .../weatherapi/response/Location.java | 16 +++ .../response/TimeLineDailyValue.java | 6 + .../weatherapi/response/TimelineDaily.java | 6 + .../weatherapi/response/TimelineHourly.java | 6 + .../response/TimelineHourlyValue.java | 5 + .../weatherapi/response/Timelines.java | 6 + .../weatherapi/response/WeatherLocation.java | 6 + .../java/dev/clanders/weatherapp/App.java | 9 ++ .../dev/clanders/weatherapp/WeatherApp.java | 27 ++++ .../weatherapp/WeatherAppController.java | 89 +++++++++++++ .../error/ApiNotResponsiveException.java | 4 + .../error/InvalidCountryException.java | 5 + src/main/resources/WeatherApp.fxml | 66 ++++++++++ 24 files changed, 546 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/encodings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 pom.xml create mode 100644 src/main/java/dev/clanders/weatherapi/Api.java create mode 100644 src/main/java/dev/clanders/weatherapi/request/SearchLocation.java create mode 100644 src/main/java/dev/clanders/weatherapi/request/SearchLocationBuilder.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/Location.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/TimeLineDailyValue.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/TimelineDaily.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/TimelineHourly.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/TimelineHourlyValue.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/Timelines.java create mode 100644 src/main/java/dev/clanders/weatherapi/response/WeatherLocation.java create mode 100644 src/main/java/dev/clanders/weatherapp/App.java create mode 100644 src/main/java/dev/clanders/weatherapp/WeatherApp.java create mode 100644 src/main/java/dev/clanders/weatherapp/WeatherAppController.java create mode 100644 src/main/java/dev/clanders/weatherapp/error/ApiNotResponsiveException.java create mode 100644 src/main/java/dev/clanders/weatherapp/error/InvalidCountryException.java create mode 100644 src/main/resources/WeatherApp.fxml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..869c305 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..de4b033 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7cb0b36 --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + dev.clanders + weather-app + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + + + + + + com.google.code.gson + gson + 2.10.1 + + + org.openjfx + javafx-fxml + 21 + + + org.openjfx + javafx-controls + 21 + + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + App + + + + + + \ No newline at end of file diff --git a/src/main/java/dev/clanders/weatherapi/Api.java b/src/main/java/dev/clanders/weatherapi/Api.java new file mode 100644 index 0000000..0ea7d28 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/Api.java @@ -0,0 +1,27 @@ +package dev.clanders.weatherapi; + +import dev.clanders.weatherapi.request.SearchLocation; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class Api { + private static String API_KEY = "08Yqt7FJVrndj56jsasgceKdppJ4yu90"; + public static HttpResponse getWeatherByLocation(SearchLocation searchLocation) throws IOException, InterruptedException { + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(String.format("https://api.tomorrow.io/v4/weather/forecast?location=%s %s&units=imperial&apikey=%s", searchLocation.city, searchLocation.state, API_KEY).replace(" ", "%20"))) + .header("accept", "application/json") + .method("GET", HttpRequest.BodyPublishers.noBody()) + .build(); + + return HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); + } + + public static void setApiKey(String newApiKey) { + API_KEY = newApiKey; + } +} diff --git a/src/main/java/dev/clanders/weatherapi/request/SearchLocation.java b/src/main/java/dev/clanders/weatherapi/request/SearchLocation.java new file mode 100644 index 0000000..276b4fc --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/request/SearchLocation.java @@ -0,0 +1,6 @@ +package dev.clanders.weatherapi.request; + +public class SearchLocation { + public String city; + public String state; +} diff --git a/src/main/java/dev/clanders/weatherapi/request/SearchLocationBuilder.java b/src/main/java/dev/clanders/weatherapi/request/SearchLocationBuilder.java new file mode 100644 index 0000000..3bbb3ad --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/request/SearchLocationBuilder.java @@ -0,0 +1,12 @@ +package dev.clanders.weatherapi.request; + +public class SearchLocationBuilder { + public static SearchLocation createLocation(String locationStr) { + SearchLocation searchLocation = new SearchLocation(); + String[] locationTokenized = locationStr.split(","); + searchLocation.city = locationTokenized[0].trim(); + searchLocation.state = locationTokenized[1].trim(); // Refactor this to intake fullname or Abbrev. + + return searchLocation; + } +} diff --git a/src/main/java/dev/clanders/weatherapi/response/Location.java b/src/main/java/dev/clanders/weatherapi/response/Location.java new file mode 100644 index 0000000..c6b075c --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/Location.java @@ -0,0 +1,16 @@ +package dev.clanders.weatherapi.response; + +public class Location { + public double lat; + public double lon; + public String name; + public String type; + + public String getCountry() { + // Probably not efficient to be splitting this string every time, but does + // it really matter in an app this size???? + String[] locationValues = name.split(","); + + return locationValues[locationValues.length - 1].trim(); + } +} diff --git a/src/main/java/dev/clanders/weatherapi/response/TimeLineDailyValue.java b/src/main/java/dev/clanders/weatherapi/response/TimeLineDailyValue.java new file mode 100644 index 0000000..996707f --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/TimeLineDailyValue.java @@ -0,0 +1,6 @@ +package dev.clanders.weatherapi.response; + +public class TimeLineDailyValue { + public double temperatureMax; + public double temperatureMin; +} diff --git a/src/main/java/dev/clanders/weatherapi/response/TimelineDaily.java b/src/main/java/dev/clanders/weatherapi/response/TimelineDaily.java new file mode 100644 index 0000000..ff93c93 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/TimelineDaily.java @@ -0,0 +1,6 @@ +package dev.clanders.weatherapi.response; + +public class TimelineDaily { + public String time; + public TimeLineDailyValue values; +} diff --git a/src/main/java/dev/clanders/weatherapi/response/TimelineHourly.java b/src/main/java/dev/clanders/weatherapi/response/TimelineHourly.java new file mode 100644 index 0000000..83b8eba --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/TimelineHourly.java @@ -0,0 +1,6 @@ +package dev.clanders.weatherapi.response; + +public class TimelineHourly { + public String time; + public TimelineHourlyValue values; +} diff --git a/src/main/java/dev/clanders/weatherapi/response/TimelineHourlyValue.java b/src/main/java/dev/clanders/weatherapi/response/TimelineHourlyValue.java new file mode 100644 index 0000000..d0898e1 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/TimelineHourlyValue.java @@ -0,0 +1,5 @@ +package dev.clanders.weatherapi.response; + +public class TimelineHourlyValue { + public double temperature; +} diff --git a/src/main/java/dev/clanders/weatherapi/response/Timelines.java b/src/main/java/dev/clanders/weatherapi/response/Timelines.java new file mode 100644 index 0000000..04c2c81 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/Timelines.java @@ -0,0 +1,6 @@ +package dev.clanders.weatherapi.response; + +public class Timelines { + public TimelineDaily[] daily; + public TimelineHourly[] hourly; +} diff --git a/src/main/java/dev/clanders/weatherapi/response/WeatherLocation.java b/src/main/java/dev/clanders/weatherapi/response/WeatherLocation.java new file mode 100644 index 0000000..ee9691a --- /dev/null +++ b/src/main/java/dev/clanders/weatherapi/response/WeatherLocation.java @@ -0,0 +1,6 @@ +package dev.clanders.weatherapi.response; + +public class WeatherLocation { + public Location location; + public Timelines timelines; +} diff --git a/src/main/java/dev/clanders/weatherapp/App.java b/src/main/java/dev/clanders/weatherapp/App.java new file mode 100644 index 0000000..47ca4af --- /dev/null +++ b/src/main/java/dev/clanders/weatherapp/App.java @@ -0,0 +1,9 @@ +package dev.clanders.weatherapp; + +public class App { + // For some reason main doesn't work within the Application class + // So gotta put it here. + public static void main(String[] args) { + WeatherApp.run(args); + } +} diff --git a/src/main/java/dev/clanders/weatherapp/WeatherApp.java b/src/main/java/dev/clanders/weatherapp/WeatherApp.java new file mode 100644 index 0000000..9b04c12 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapp/WeatherApp.java @@ -0,0 +1,27 @@ +package dev.clanders.weatherapp; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +import java.io.IOException; +import java.util.Objects; + +public class WeatherApp extends Application { + public static void run(String[] args) { + launch(args); + } + + @Override + public void start(Stage stage) throws IOException { + Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("/WeatherApp.fxml"))); + + Scene scene = new Scene(root, 600, 400); + + stage.setTitle("FXML Welcome"); + stage.setScene(scene); + stage.show(); + } +} diff --git a/src/main/java/dev/clanders/weatherapp/WeatherAppController.java b/src/main/java/dev/clanders/weatherapp/WeatherAppController.java new file mode 100644 index 0000000..adcb235 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapp/WeatherAppController.java @@ -0,0 +1,89 @@ +package dev.clanders.weatherapp; + +import com.google.gson.Gson; +import dev.clanders.weatherapi.Api; +import dev.clanders.weatherapi.request.SearchLocation; +import dev.clanders.weatherapi.request.SearchLocationBuilder; +import dev.clanders.weatherapi.response.WeatherLocation; +import dev.clanders.weatherapp.error.ApiNotResponsiveException; +import dev.clanders.weatherapp.error.InvalidCountryException; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.paint.Paint; + +import java.net.http.HttpResponse; +import java.util.Objects; + +public class WeatherAppController { + @FXML + private TextField locationTextField; + @FXML + private Label cityNameLabel; + @FXML + private Label locationLowLabel; + @FXML + private Label locationHighLabel; + @FXML + private Label locationCurrentLabel; + @FXML + private VBox forecastContainer; + @FXML + private TextField usersApiKeyTextField; + @FXML + private Label userApiKeyMessageLabel; + + @FXML protected void handleLocationEnteredAction(ActionEvent event) { + cityNameLabel.setTextFill(Color.BLACK); + cityNameLabel.setStyle("-fx-font-size: 24;"); + + try { + SearchLocation searchLocation = SearchLocationBuilder.createLocation(locationTextField.getText()); // This doesn't fail gracefully + HttpResponse res = Api.getWeatherByLocation(searchLocation); + + Gson g = new Gson(); + WeatherLocation wLocation = g.fromJson(res.body(), WeatherLocation.class); + + if (wLocation.location == null) { + throw new ApiNotResponsiveException(); + } + + if (!Objects.equals(wLocation.location.getCountry(), "United States")) { + throw new InvalidCountryException(); + } + + System.out.printf("Location: %s\n", wLocation.location.name); + cityNameLabel.setText(wLocation.location.name); + forecastContainer.setVisible(true); + + locationLowLabel.setText(String.valueOf(wLocation.timelines.daily[0].values.temperatureMin)); + locationHighLabel.setText(String.valueOf(wLocation.timelines.daily[0].values.temperatureMax)); + locationCurrentLabel.setText((String.valueOf(wLocation.timelines.hourly[0].values.temperature))); + } catch(ApiNotResponsiveException e) { + cityNameLabel.setStyle("-fx-font-size: 12;"); + cityNameLabel.setTextFill(Color.RED); + cityNameLabel.setText("API currently unresponsive or Invalid custom API Key. \nThis application uses the free tier of tomorrow.io.\n" + + "Due to hourly limits of API calls, you will need to wait before making further requests. Or provide a new API Key below."); + usersApiKeyTextField.setVisible(true); + } + catch (InvalidCountryException e) { + cityNameLabel.setStyle("-fx-font-size: 12;"); + cityNameLabel.setTextFill(Color.RED); + cityNameLabel.setText("City must be within United States.\n Please use format City, State without abbreviations."); + } + catch (Exception e) { + cityNameLabel.setTextFill(Color.RED); + cityNameLabel.setText("INVALID"); + e.printStackTrace(); + } + } + + @FXML protected void handleApiKeyEnteredAction(ActionEvent event) { + Api.setApiKey(usersApiKeyTextField.getText()); + usersApiKeyTextField.setVisible(false); + userApiKeyMessageLabel.setVisible(true); + } +} diff --git a/src/main/java/dev/clanders/weatherapp/error/ApiNotResponsiveException.java b/src/main/java/dev/clanders/weatherapp/error/ApiNotResponsiveException.java new file mode 100644 index 0000000..2aa4cf9 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapp/error/ApiNotResponsiveException.java @@ -0,0 +1,4 @@ +package dev.clanders.weatherapp.error; + +public class ApiNotResponsiveException extends Exception { +} diff --git a/src/main/java/dev/clanders/weatherapp/error/InvalidCountryException.java b/src/main/java/dev/clanders/weatherapp/error/InvalidCountryException.java new file mode 100644 index 0000000..4697105 --- /dev/null +++ b/src/main/java/dev/clanders/weatherapp/error/InvalidCountryException.java @@ -0,0 +1,5 @@ +package dev.clanders.weatherapp.error; + +public class InvalidCountryException extends Exception{ + +} diff --git a/src/main/resources/WeatherApp.fxml b/src/main/resources/WeatherApp.fxml new file mode 100644 index 0000000..e4345bd --- /dev/null +++ b/src/main/resources/WeatherApp.fxml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +