commit 9d87bf4575822c670910d2367124268e129a8d3b Author: Cole Landers Date: Thu Dec 21 08:24:10 2023 -0600 init commit 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +