cs-3443: bring LakeWatch in-tree

This commit is contained in:
Price Hiller 2024-08-31 02:13:52 -05:00
parent 09c66095ed
commit bdc1a6f44a
Signed by: Price
GPG Key ID: C3FADDE7A8534BEB
203 changed files with 41502 additions and 0 deletions

View File

@ -0,0 +1,11 @@
export APP_API_HOST="localhost"
export APP_API_PORT="8000"
export APP_DATABASE_HOST="localhost"
export APP_DATABASE_PORT="5432"
export APP_DATABASE_USERNAME="postgres"
export APP_DATABASE_PASSWORD="password"
export APP_DATABASE_NAME="lakewatch"
export APP_DATABASE_REQUIRE_SSL="false"
export DATABASE_URL="postgres://${APP_DATABASE_USERNAME}:${APP_DATABASE_PASSWORD}@${APP_DATABASE_HOST}:${APP_DATABASE_PORT}/${APP_DATABASE_NAME}"

View File

@ -0,0 +1 @@
.idea/

View File

@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

View File

@ -0,0 +1,3 @@
# `LakeWatch`
This repository contains the actual Android application.

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,43 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace = "edu.utsa.cs3443.lakewatch"
compileSdk = 34
defaultConfig {
applicationId = "edu.utsa.cs3443.lakewatch"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}

View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package edu.utsa.cs3443.lakewatch;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("edu.utsa.cs3443.lakewatch", appContext.getPackageName());
}
}

View File

@ -0,0 +1,43 @@
<?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:name=".model.userSettings"
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.LakeWatch"
tools:targetApi="31">
<activity
android:name=".MainRampMenuActivity"
android:exported="false" />
<activity
android:name=".BoatRampActivity"
android:exported="false" />
<activity
android:name=".WaterLevelActivity"
android:exported="false" />
<activity
android:name=".WeatherActivity"
android:exported="false" />
<activity
android:name=".SettingsActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,111 @@
package edu.utsa.cs3443.lakewatch;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import edu.utsa.cs3443.lakewatch.model.BoatRampData;
import edu.utsa.cs3443.lakewatch.model.BoatRampStatus;
public class BoatRampActivity extends AppCompatActivity {
/**
* Drive the view for a single boat ramp page
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_boat_ramp);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
BoatRampData rampData = (BoatRampData) this.getIntent().getExtras().getSerializable("rampData");
((TextView)this.findViewById(R.id.rampName)).setText(rampData.getName());
ImageView rampImage = this.findViewById(R.id.rampImage);
for (Field field : R.drawable.class.getFields()) {
String fieldName = field.getName();
if (fieldName.equals("ramp" + rampData.getRampNumber())) {
rampImage.setImageResource(this.getResources().getIdentifier(field.getName(), "drawable", this.getPackageName()));
}
}
ImageView statusIcon = this.findViewById(R.id.statusIcon);
GradientDrawable statusShape = new GradientDrawable();
statusShape.setStroke(3, Color.parseColor("#000000"));
statusShape.setCornerRadius(8000);
if (rampData.getStatus() == BoatRampStatus.OPEN) {
statusIcon.setImageResource(R.drawable.circle_check);
statusShape.setColor(ContextCompat.getColor(this, R.color.rampStatusOpen));
} else if (rampData.getStatus() == BoatRampStatus.CLOSED) {
statusIcon.setImageResource(R.drawable.circle_x);
statusShape.setColor(ContextCompat.getColor(this, R.color.rampStatusClosed));
} else {
statusIcon.setImageResource(R.drawable.circle_guess);
statusShape.setColor(ContextCompat.getColor(this, R.color.rampStatusUnknown));
}
CardView statusCard = this.findViewById(R.id.statusCard);
statusCard.setBackground(statusShape);
((TextView)this.findViewById(R.id.statusText)).setText(rampData.getStatus().toString());
GradientDrawable cardShape = new GradientDrawable();
cardShape.setColor(ContextCompat.getColor(this, R.color.rampInfoBackground));
cardShape.setStroke(3, Color.parseColor("#000000"));
cardShape.setCornerRadius(8000);
CardView openTimesCard = this.findViewById(R.id.openTimes);
((TextView)openTimesCard.findViewById(R.id.openTimesText)).setText(rampData.getOpenTimes());
openTimesCard.setBackground(cardShape);
CardView addressCard = this.findViewById(R.id.address);
((TextView)addressCard.findViewById(R.id.addressText)).setText(rampData.getAddress());
addressCard.setBackground(cardShape);
addressCard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("", rampData.getAddress());
clipboard.setPrimaryClip(clip);
Toast toast = Toast.makeText(view.getContext(), "Address copied to clipboard!", Toast.LENGTH_SHORT);
View toastView = toast.getView();
toastView.findViewById(android.R.id.message).setBackgroundColor(Color.TRANSPARENT);
toastView.getBackground().setTint(ContextCompat.getColor(toastView.getContext(), R.color.buttonBackgroundColor));
toast.show();
}
});
CardView operatorCard = this.findViewById(R.id.operator);
((TextView)operatorCard.findViewById(R.id.operatorText)).setText(rampData.getOperator());
operatorCard.setBackground(cardShape);
CardView lastUpdatedCard = this.findViewById(R.id.lastUpdated);
((TextView)lastUpdatedCard.findViewById(R.id.lastUpdatedText)).setText(DateTimeFormatter.ofPattern("MM/dd/yyyy").format(rampData.getLastUpdated()));
lastUpdatedCard.setBackground(cardShape);
}
}

View File

@ -0,0 +1,92 @@
package edu.utsa.cs3443.lakewatch;
import android.content.Intent;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
//Button Initialization(s)
Button weatherButton = findViewById(R.id.button); //Weather
Button waterStatusButton = findViewById(R.id.button2); //WaterStatus
Button rampsButton = findViewById(R.id.button3); //Ramps
Button settingsButton = findViewById(R.id.settingsButton); // Settings
//Weather View OnClickListener
weatherButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) { openWeatherActivity(); }
});
//Water Status View OnClickListener
waterStatusButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) { openWaterLevelActivity();}
});
//Boat Ramps View OnClickListener
rampsButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v)
{
openMainRampMenuActivity();
}
});
//Settings View OnClickListener
settingsButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
openSettingsActivity();
}
});
}
//This function is used to navigate to the settings menu via the intent object
private void openSettingsActivity()
{
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
}
private void openWeatherActivity()
{
Intent intent = new Intent(this, WeatherActivity.class);
startActivity(intent);
}
private void openWaterLevelActivity()
{
Intent intent = new Intent(this, WaterLevelActivity.class);
startActivity(intent);
}
private void openMainRampMenuActivity()
{
Intent intent = new Intent(this, MainRampMenuActivity.class);
startActivity(intent);
}
}

View File

@ -0,0 +1,166 @@
package edu.utsa.cs3443.lakewatch;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import edu.utsa.cs3443.lakewatch.model.BoatRampData;
import edu.utsa.cs3443.lakewatch.model.BoatRampStatus;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MainRampMenuActivity extends AppCompatActivity {
private ExecutorService executorService;
/**
* The main driver behind the full listing of the boat ramps
*
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main_ramp_menu);
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main),
(v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
executorService = Executors.newSingleThreadExecutor();
Callable<ArrayList<BoatRampData>> fetchRampDataTask =
() -> {
ArrayList<BoatRampData> fetchedData = BoatRampData.fetchData();
BoatRampData.saveData(this.findViewById(R.id.main).getContext(), fetchedData);
return fetchedData;
};
Future<ArrayList<BoatRampData>> future = executorService.submit(fetchRampDataTask);
new Handler(Looper.getMainLooper())
.post(
() -> {
ArrayList<BoatRampData> allRampData = new ArrayList<>();
try {
allRampData = future.get();
} catch (Exception e) {
Toast toast =
Toast.makeText(
this.findViewById(R.id.main).getContext(),
"API Fetch Failed, failling back to cache.",
Toast.LENGTH_SHORT);
View toastView = toast.getView();
toastView.findViewById(android.R.id.message).setBackgroundColor(Color.TRANSPARENT);
toastView
.getBackground()
.setTint(
ContextCompat.getColor(
toastView.getContext(), R.color.buttonBackgroundColor));
toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 50);
toast.show();
Toast cacheToast =
Toast.makeText(
this.findViewById(R.id.main).getContext(),
"Failed to load boat ramp data!",
Toast.LENGTH_SHORT);
View cacheToastView = cacheToast.getView();
cacheToastView
.findViewById(android.R.id.message)
.setBackgroundColor(Color.TRANSPARENT);
cacheToastView
.getBackground()
.setTint(
ContextCompat.getColor(
cacheToastView.getContext(), R.color.buttonBackgroundColor));
cacheToast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 50);
try {
allRampData =
BoatRampData.loadCachedData(this.findViewById(R.id.main).getContext());
} catch (Exception ex) {
cacheToast.show();
}
}
LinearLayout rampListing = this.findViewById(R.id.rampListing);
int ramps_open = 0;
LayoutInflater inflater =
(LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (BoatRampData rampData : allRampData) {
if (rampData.getStatus() == BoatRampStatus.OPEN) {
ramps_open++;
}
View rampView = inflater.inflate(R.layout.ramp_menu_card, rampListing, false);
CardView rampMenuCard = rampView.findViewById(R.id.rampMenuCard);
((TextView) rampMenuCard.findViewById(R.id.rampName)).setText(rampData.getName());
((TextView) rampMenuCard.findViewById(R.id.rampStatus))
.setText(rampData.getStatus().toString());
ImageView statusImage = rampView.findViewById(R.id.statusIcon);
GradientDrawable shape = new GradientDrawable();
if (rampData.getStatus() == BoatRampStatus.OPEN) {
statusImage.setImageResource(R.drawable.circle_check);
shape.setColor(ContextCompat.getColor(this, R.color.rampStatusOpen));
} else if (rampData.getStatus() == BoatRampStatus.CLOSED) {
statusImage.setImageResource(R.drawable.circle_x);
shape.setColor(ContextCompat.getColor(this, R.color.rampStatusClosed));
} else {
statusImage.setImageResource(R.drawable.circle_guess);
shape.setColor(ContextCompat.getColor(this, R.color.rampStatusUnknown));
}
shape.setStroke(3, Color.parseColor("#000000"));
shape.setCornerRadius(80);
rampMenuCard.setBackground(shape);
rampMenuCard.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
launchBoatRampActivity(rampData);
}
});
rampListing.addView(rampView);
}
((TextView) this.findViewById(R.id.rampsOpen))
.setText(String.format("%d/%d Ramps Open", ramps_open, allRampData.size()));
});
}
/**
* Launch a specific page of a boat ramp
*
* @param rampData The ramp data to use for the page
*/
private void launchBoatRampActivity(BoatRampData rampData) {
Intent intent = new Intent(this, BoatRampActivity.class);
intent.putExtra("rampData", (Serializable) rampData);
startActivity(intent);
}
}

View File

@ -0,0 +1,167 @@
package edu.utsa.cs3443.lakewatch;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import edu.utsa.cs3443.lakewatch.model.BoatRampData;
import edu.utsa.cs3443.lakewatch.model.WaterLevelData;
import edu.utsa.cs3443.lakewatch.model.WeatherData;
public class SettingsActivity extends AppCompatActivity {
private ExecutorService executorService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_settings);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
//Button Initializations
Button btnToggleDark = findViewById(R.id.button5);
Button dataFetchButton = findViewById(R.id.button6);
Button clearCacheButton = findViewById(R.id.button7);
//Saving state of our app
//using SharedPreferences
//necessary for Dark Mode
SharedPreferences sharedPreferences = getSharedPreferences("sharedPrefs",MODE_PRIVATE);
final SharedPreferences.Editor editor = sharedPreferences.edit();
final boolean isDarkModeOn = sharedPreferences.getBoolean("isDarkModeOn",false);
// When user reopens the app
// after applying a respective
// color mode
if(isDarkModeOn)
{
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
btnToggleDark.setText("DARK MODE ON");
}
else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
btnToggleDark.setText("DARK MODE OFF");
}
//Dark Mode OnClickListener
//contains the logic to toggle
//Dark mode
btnToggleDark.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
// When user taps the enable/disable
// dark mode button
if (isDarkModeOn) {
// if dark mode is on
// it will turn off dark mode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
// sets isDarkModeOn
// boolean to false
editor.putBoolean("isDarkModeOn", false);
editor.apply();
}
else {
// if dark mode is off
// it will turn on dark mode
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
// sets isDarkModeOn
// boolean to true
editor.putBoolean("isDarkModeOn", true);
editor.apply();
}
}
});
dataFetchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast toast = Toast.makeText(view.getContext(), "Data Fetched.", Toast.LENGTH_SHORT);
View toastView = toast.getView();
toastView.findViewById(android.R.id.message).setBackgroundColor(Color.TRANSPARENT);
toastView.getBackground().setTint(ContextCompat.getColor(toastView.getContext(), R.color.buttonBackgroundColor));
toast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM,0,50);
executorService = Executors.newSingleThreadExecutor();
Callable<ArrayList<BoatRampData>> fetchRampDataTask =
() -> {
ArrayList<BoatRampData> fetchedData = BoatRampData.fetchData();
BoatRampData.saveData(view.getContext(), fetchedData);
return fetchedData;
};
Future<ArrayList<BoatRampData>> future = executorService.submit(fetchRampDataTask);
new Handler(Looper.getMainLooper()).post(() -> {
try {
BoatRampData.saveData(view.getContext(), future.get());
} catch (Exception e) {
toast.setText("Failed to force a data fetch!");
}
});
// Fetch Water Level Data
toast.show();
WaterLevelData.forceDataFetch(getApplicationContext());
}
});
clearCacheButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast toast = Toast.makeText(view.getContext(), "Cache Cleared.", Toast.LENGTH_SHORT);
View toastView = toast.getView();
toastView.findViewById(android.R.id.message).setBackgroundColor(Color.TRANSPARENT);
toastView.getBackground().setTint(ContextCompat.getColor(toastView.getContext(), R.color.buttonBackgroundColor));
toast.setGravity(Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM,0,50);
try {
BoatRampData.wipeCache(view.getContext());
} catch (IOException e) {
toast.setText("Failed to wipe ramp data cache!");
}
toast.show();
try {
WeatherData.wipeCache(view.getContext());
} catch (IOException e) {
toast.setText("Failed to wipe weather data cache!");
}
toast.show();
WaterLevelData.clearCache(getApplicationContext());
}
});
}
}

View File

@ -0,0 +1,117 @@
package edu.utsa.cs3443.lakewatch;
import android.os.AsyncTask;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.ArrayList;
import edu.utsa.cs3443.lakewatch.model.WaterLevelData;
public class WaterLevelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_water);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
new FetchWaterLevelDataTask().execute();
}
private class FetchWaterLevelDataTask extends AsyncTask<Void, Void, WaterLevelData> {
@Override
protected WaterLevelData doInBackground(Void... voids) {
WaterLevelData data = null;
try {
String jsonResponse = WaterLevelData.getJsonResponseFromApi();
data = WaterLevelData._loadWaterLevelData(jsonResponse);
if (data != null) {
data.saveWaterLevelData(WaterLevelActivity.this);
}
} catch (Exception e) {
e.printStackTrace();
}
if (data == null) {
data = WaterLevelData.loadSavedWaterLevelData(WaterLevelActivity.this);
}
return data;
}
@Override
protected void onPostExecute(WaterLevelData currentData) {
updateUI(currentData);
}
}
private void updateUI(WaterLevelData currentData) {
TableLayout tableLayout = findViewById(R.id.tableLayout);
TableRow row1 = (TableRow) tableLayout.getChildAt(0);
TextView row1Column2 = (TextView) row1.getChildAt(1);
if (currentData != null) {
row1Column2.setText(String.format("%.2f%%", currentData.getPercentFull()));
TableRow row2 = (TableRow) tableLayout.getChildAt(1);
TextView row2Column2 = (TextView) row2.getChildAt(1);
row2Column2.setText(String.format("%.2f", currentData.getWaterLevel()));
TableRow row3 = (TableRow) tableLayout.getChildAt(2);
TextView row3Column2 = (TextView) row3.getChildAt(1);
row3Column2.setText(String.format("%.2f", currentData.getSurfaceArea()));
TextView waterLevelValue = findViewById(R.id.waterLevelValue);
TextView dateTime = findViewById(R.id.dateTime);
TextView levelStatus = findViewById(R.id.levelStatus);
waterLevelValue.setText(String.format("%.2f", currentData.getWaterLevel()));
dateTime.setText(currentData.getDate());
levelStatus.setText(String.format("Level is %.2f feet below full pool of 909.00 ft", 909.00 - currentData.getWaterLevel()));
} else {
TextView waterLevelValue = findViewById(R.id.waterLevelValue);
TextView dateTime = findViewById(R.id.dateTime);
TextView levelStatus = findViewById(R.id.levelStatus);
waterLevelValue.setText("0.0");
dateTime.setText("N/A");
levelStatus.setText("No data available");
row1 = (TableRow) tableLayout.getChildAt(0);
row1Column2 = (TextView) row1.getChildAt(1);
row1Column2.setText("0.0%");
TableRow row2 = (TableRow) tableLayout.getChildAt(1);
TextView row2Column2 = (TextView) row2.getChildAt(1);
row2Column2.setText("0.0");
TableRow row3 = (TableRow) tableLayout.getChildAt(2);
TextView row3Column2 = (TextView) row3.getChildAt(1);
row3Column2.setText("0.0");
}
}
}

View File

@ -0,0 +1,131 @@
package edu.utsa.cs3443.lakewatch;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import edu.utsa.cs3443.lakewatch.model.WeatherData;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class WeatherActivity extends AppCompatActivity {
private TextView weatherDescription;
private TextView temperature;
private TextView windDirection;
private TextView windSpeed;
private TextView windGust;
private TextView chancePrecipitation;
private TextView amountPrecipitation;
private ExecutorService executorService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_weather);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
weatherDescription = findViewById(R.id.weather_description);
temperature = findViewById(R.id.temperature);
windDirection = findViewById(R.id.wind_direction);
windSpeed = findViewById(R.id.wind_speed);
windGust = findViewById(R.id.wind_gust);
chancePrecipitation = findViewById(R.id.chance_precipitation);
amountPrecipitation = findViewById(R.id.amount_precipitation);
// Initialize the ExecutorService
executorService = Executors.newSingleThreadExecutor();
// Fetch weather data using ExecutorService
fetchWeatherData();
Button weatherButton = findViewById(R.id.weather_button);
weatherButton.setOnClickListener(v -> {
String url = "https://forecast.weather.gov/MapClick.php?lat=29.699301&lon=-98.115109";
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
});
}
// Fetch weather data and update UI
private void fetchWeatherData() {
Callable<WeatherData> fetchWeatherTask = () -> {
try {
WeatherData weatherData = new WeatherData();
WeatherData result = weatherData.loadWeatherData();
Log.d("WeatherActivity", "WeatherData loaded: " + result);
return result;
} catch (IOException e) {
e.printStackTrace();
return null;
}
};
Future<WeatherData> future = executorService.submit(fetchWeatherTask);
// Update the UI on the main thread
new Handler(Looper.getMainLooper()).post(() -> {
try {
WeatherData weatherData = future.get();
if (weatherData != null) {
weatherData.saveWeatherData(WeatherActivity.this);
updateUIWithWeatherData(weatherData);
} else {
weatherData = WeatherData.loadSavedWeatherData(WeatherActivity.this);
if (weatherData != null) {
updateUIWithWeatherData(weatherData);
} else {
weatherDescription.setText("No saved weather data available");
}
}
} catch (Exception e) {
e.printStackTrace();
weatherDescription.setText("Error fetching weather data");
}
});
}
private void updateUIWithWeatherData(WeatherData weatherData) {
weatherDescription.setText(weatherData.getWeatherDescription());
temperature.setText(String.format("H:%d°F L:%d°F", weatherData.getTempHigh(), weatherData.getTempLow()));
String windDirectionValue = weatherData.getWindDirection();
if (windDirectionValue.isEmpty()) {
windDirectionValue = "N/A";
}
windDirection.setText(String.format("%s", windDirectionValue));
windSpeed.setText(weatherData.getWindSpeed());
windGust.setText(String.format("%.0f mph", weatherData.getWindGust()));
chancePrecipitation.setText(String.format("%d%% Chance Precipitation", weatherData.getChancePrecipitation()));
amountPrecipitation.setText(String.format("%.1f\u2033 Precipitation Next 24 HR", weatherData.getAmountPrecipitation()));
}
@Override
protected void onDestroy() {
super.onDestroy();
// Shut down the ExecutorService when the activity is destroyed
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdown();
}
}
}

View File

@ -0,0 +1,318 @@
package edu.utsa.cs3443.lakewatch.model;
import android.content.Context;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Model class containing logic and data for all boat ramps
*
*/
public class BoatRampData implements Serializable {
private Integer rampNumber;
private String name;
private BoatRampStatus status;
private String openTimes;
private String address;
private String operator;
private LocalDate lastUpdated;
private static final String CACHE_FILE_NAME = "BoatRampData.json";
/**
* Create a single boat ramp instance
*
* @param rampNumber The official ramp number
* @param name The more commonly known name of the ramp
* @param status The open/closed/unknown status of the ramp
* @param openTimes The times the ramp can be used if its status is "open"
* @param address The approximate address of the ramp
* @param operator The owner of the ramp, like Comal County
* @param lastUpdated The last time the given data was updated
*/
public BoatRampData(
Integer rampNumber,
String name,
BoatRampStatus status,
String openTimes,
String address,
String operator,
LocalDate lastUpdated) {
this.setRampNumber(rampNumber);
this.setName(name);
this.setStatus(status);
this.setOpenTimes(openTimes);
this.setAddress(address);
this.setOperator(operator);
this.setLastUpdated(lastUpdated);
}
/**
* Get the common name of the ramp
*
* @return name of the ramp
*/
public String getName() {
return name;
}
/**
* Set the common name of the ramp
*
* @param name common name of the ramp
*/
public void setName(String name) {
this.name = name;
}
/**
* Get the open/closed/unknown status of the ramp
*
* @return status of the ramp
*/
public BoatRampStatus getStatus() {
return status;
}
/**
* Set the open/closed/unknown status of the ramp
*
* @param status new status for the ramp
*/
public void setStatus(BoatRampStatus status) {
this.status = status;
}
/**
* Get the operating times of the ramp
*
* @return operating times of the ramp
*/
public String getOpenTimes() {
return openTimes;
}
/**
* Set the operating times of the ramp
*
* @param openTimes new operating times for the ramp
*/
public void setOpenTimes(String openTimes) {
this.openTimes = openTimes;
}
/**
* Get the approximate address of the ramp
*
* @return address of the ramp
*/
public String getAddress() {
return address;
}
/**
* Set the address of the ramp
*
* @param address new address for the ramp
*/
public void setAddress(String address) {
this.address = address;
}
/**
* Get the operator of the ramp
*
* @return operator of the ramp
*/
public String getOperator() {
return operator;
}
/**
* Set the operator of the ramp
*
* @param operator new operator for the ramp
*/
public void setOperator(String operator) {
this.operator = operator;
}
/**
* Get the last time the given data set was updated
*
* @return last updated time for the ramp data
*/
public LocalDate getLastUpdated() {
return lastUpdated;
}
/**
* Update the last updated time for the ramp
*
* @param lastUpdated new update time for the ramp's data
*/
public void setLastUpdated(LocalDate lastUpdated) {
this.lastUpdated = lastUpdated;
}
/**
* Create a full listing of all the ramp's data from a JSON Array
*
* @param in The json stream to parse
* @return A full listing of boat ramp data
* @throws IOException when readers cannot be created
* @throws JSONException when the input stream contains invalid json
*/
private static ArrayList<BoatRampData> fromJsonStream(InputStream in)
throws IOException, JSONException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
out.append(line);
}
reader.close();
ArrayList<BoatRampData> rampData = new ArrayList<>();
JSONArray json = new JSONArray(out.toString());
for (int i = 0; i < json.length(); i++) {
JSONObject rampJson = json.getJSONObject(i);
rampData.add(BoatRampData.fromJson(rampJson));
}
rampData.sort(Comparator.comparing(BoatRampData::getStatus));
return rampData;
}
/**
* Fetch the latest ramp data from the API
*
* @return An updated full listing of all boat ramp data
* @throws Exception When the request fails in any shape way or form
*/
public static ArrayList<BoatRampData> fetchData() throws Exception {
HttpURLConnection conn =
(HttpURLConnection)
new URL("https://lakewatch.orion-technologies.io/v1/ramps").openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
return BoatRampData.fromJsonStream(conn.getInputStream());
}
/**
* Load's the data from a cached file on disk if it exists
*
* @param context a given android context, used to lookup common paths
* @return The last cached full listing of all boat ramp data
* @throws IOException If the cache file cannot be read or doesn't exist
* @throws JSONException If the data contained within the file is invalid
*/
public static ArrayList<BoatRampData> loadCachedData(Context context)
throws IOException, JSONException {
File file = new File(context.getCacheDir(), BoatRampData.CACHE_FILE_NAME);
return BoatRampData.fromJsonStream(Files.newInputStream(file.toPath()));
}
/**
* Wipe the cache file on disk
*
* @param context the android context used to look up the cache directory
* @throws IOException If the file cannot be deleted and it exists
*/
public static void wipeCache(Context context) throws IOException {
File file = new File(context.getCacheDir(), BoatRampData.CACHE_FILE_NAME);
if (file.exists()) {
file.delete();
}
}
/**
* Serialize a ramp's data into JSON for export
*
* @return The json representation of the ramp
* @throws JSONException If it's not possible to parse the ramp data into JSON
*/
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("number", this.getRampNumber());
json.put("name", this.getName());
json.put("address", this.getAddress());
json.put("operator", this.getOperator());
json.put("status", this.getStatus().toString());
json.put(
"last_updated", this.getLastUpdated().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
json.put("operating_times", this.getOpenTimes());
return json;
}
/**
* Deserialize a given ramp's data from JSON
*
* @param json The json object to deserialize
* @return A single boat ramp's data
* @throws JSONException If the data contains invalid json
*/
public static BoatRampData fromJson(JSONObject json) throws JSONException {
Integer rampNumber = json.getInt("number");
String name = json.getString("name");
String address = json.getString("address");
String operator = json.getString("operator");
BoatRampStatus status = BoatRampStatus.fromString(json.getString("status"));
LocalDate lastUpdated = LocalDate.parse(json.getString("last_updated"));
String openTimes = json.getString("operating_times");
return new BoatRampData(rampNumber, name, status, openTimes, address, operator, lastUpdated);
}
/**
* Save a full listing of ramp data to a cache file on disk
*
* @param context Used to get android paths on the file system
* @param rampData The full listing of ramp data to save
* @throws IOException If the cache file cannot be written to
* @throws JSONException If the ramp data cannot be serialized into JSON
*/
public static void saveData(Context context, ArrayList<BoatRampData> rampData)
throws IOException, JSONException {
File file = new File(context.getCacheDir(), BoatRampData.CACHE_FILE_NAME);
BufferedWriter bw = new BufferedWriter(new FileWriter(file.getAbsoluteFile()));
JSONArray jsonData = new JSONArray();
for (BoatRampData ramp : rampData) {
jsonData.put(ramp.toJson());
}
bw.write(jsonData.toString());
bw.close();
}
/**
* Get the number of the ramp
*
* @return number of the ramp
*/
public Integer getRampNumber() {
return rampNumber;
}
/**
* Set a new number for the ramp
*
* @param rampNumber new number for the ramp
*/
public void setRampNumber(Integer rampNumber) {
this.rampNumber = rampNumber;
}
}

View File

@ -0,0 +1,39 @@
package edu.utsa.cs3443.lakewatch.model;
import java.io.Serializable;
public enum BoatRampStatus implements Serializable {
OPEN,
CLOSED,
UNKNOWN;
/**
* Get the string representation of the current Enum
*/
public String toString() {
switch (this) {
case OPEN:
return "Open";
case CLOSED:
return "Closed";
default:
return "Unknown";
}
}
/**
* Get a boat ramp status from a String
*
* @param status The string to convert to a status enum
*/
public static BoatRampStatus fromString(String status) {
switch (status.toLowerCase()) {
case "open":
return BoatRampStatus.OPEN;
case "closed":
return BoatRampStatus.CLOSED;
default:
return BoatRampStatus.UNKNOWN;
}
}
}

View File

@ -0,0 +1,324 @@
package edu.utsa.cs3443.lakewatch.model;
import android.content.Context;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class WaterLevelData {
private Date date;
private double waterLevel;
private double surfaceArea;
private int reservoirStorage;
private int conservationStorage;
private double percentFull;
private int conservationCapacity;
private int deadPoolCapacity;
private boolean isCurrent;
public WaterLevelData(Date date, double waterLevel, boolean isCurrent) {
this.date = date;
this.waterLevel = waterLevel;
this.isCurrent = isCurrent;
this.surfaceArea = 0.0;
this.reservoirStorage = 0;
this.conservationStorage = 0;
this.percentFull = 0.0;
this.conservationCapacity = 0;
this.deadPoolCapacity = 0;
}
// Getters and Setters
public String getDate() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
public void setDate(Date date) {
this.date = date;
}
public double getWaterLevel() {
return waterLevel;
}
public void setWaterLevel(double waterLevel) {
this.waterLevel = waterLevel;
}
public double getSurfaceArea() {
return surfaceArea;
}
public void setSurfaceArea(double surfaceArea) {
this.surfaceArea = surfaceArea;
}
public int getReservoirStorage() {
return reservoirStorage;
}
public void setReservoirStorage(int reservoirStorage) {
this.reservoirStorage = reservoirStorage;
}
public int getConservationStorage() {
return conservationStorage;
}
public void setConservationStorage(int conservationStorage) {
this.conservationStorage = conservationStorage;
}
public double getPercentFull() {
return percentFull;
}
public void setPercentFull(double percentFull) {
this.percentFull = percentFull;
}
public int getConservationCapacity() {
return conservationCapacity;
}
public void setConservationCapacity(int conservationCapacity) {
this.conservationCapacity = conservationCapacity;
}
public int getDeadPoolCapacity() {
return deadPoolCapacity;
}
public void setDeadPoolCapacity(int deadPoolCapacity) {
this.deadPoolCapacity = deadPoolCapacity;
}
public boolean isCurrent() {
return isCurrent;
}
public void setCurrent(boolean isCurrent) {
this.isCurrent = isCurrent;
}
@Override
public String toString() {
return "WaterLevelData{" +
"date=" + date +
", waterLevel=" + waterLevel +
", surfaceArea=" + surfaceArea +
", reservoirStorage=" + reservoirStorage +
", conservationStorage=" + conservationStorage +
", percentFull=" + percentFull +
", conservationCapacity=" + conservationCapacity +
", deadPoolCapacity=" + deadPoolCapacity +
", isCurrent=" + isCurrent +
'}';
}
public static String getJsonResponseFromApi() {
String urlString = "https://lakewatch.orion-technologies.io/v1/waterdata?start=1900-01-01&end=3000-01-01"; //API URL
HttpURLConnection urlConnection = null;
BufferedReader reader = null;
String jsonResponse = null;
try {
URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
InputStream inputStream = urlConnection.getInputStream();
StringBuilder buffer = new StringBuilder();
if (inputStream == null) {
return null;
}
reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line).append("\n");
}
if (buffer.length() == 0) {
return null;
}
jsonResponse = buffer.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (final IOException e) {
e.printStackTrace();
}
}
}
return jsonResponse;
}
public static WaterLevelData _loadWaterLevelData(String jsonResponse) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
try {
JSONArray jsonArray = new JSONArray(jsonResponse);
if (jsonArray.length() > 0) {
JSONObject jsonObject = jsonArray.getJSONObject(jsonArray.length() - 1); // Get the last entry
Date date;
try {
date = sdf.parse(jsonObject.getString("date"));
} catch (ParseException e) {
e.printStackTrace();
date = new Date(); // Default to current date if parsing fails
}
double waterLevel = jsonObject.optDouble("water_level", 0.0);
WaterLevelData data = new WaterLevelData(date, waterLevel, true);
data.setSurfaceArea(jsonObject.optDouble("surface_area", 0.0));
data.setReservoirStorage(jsonObject.optInt("reservoir_storage", 0));
data.setConservationStorage(jsonObject.optInt("conservation_storage", 0));
data.setPercentFull(jsonObject.optDouble("percent_full", 0.0));
data.setConservationCapacity(jsonObject.optInt("conservation_capacity", 0));
data.setDeadPoolCapacity(jsonObject.optInt("dead_pool_capacity", 0));
return data;
}
} catch (Exception e) {
e.printStackTrace();
}
return null; // Return null if no data is available
}
// Save the data to a JSON file
public void saveWaterLevelData(Context context) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("date", sdf.format(date));
jsonObject.put("water_level", waterLevel);
jsonObject.put("surface_area", surfaceArea);
jsonObject.put("reservoir_storage", reservoirStorage);
jsonObject.put("conservation_storage", conservationStorage);
jsonObject.put("percent_full", percentFull);
jsonObject.put("conservation_capacity", conservationCapacity);
jsonObject.put("dead_pool_capacity", deadPoolCapacity);
File file = new File(context.getFilesDir(), "water_level_data.json");
FileWriter fileWriter = new FileWriter(file);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write(jsonObject.toString());
bufferedWriter.close();
} catch (Exception e) {
e.printStackTrace();
Log.e("WaterLevelData", "Failed to save data", e);
}
}
// Load the data from a JSON file
public static WaterLevelData loadSavedWaterLevelData(Context context) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
File file = new File(context.getFilesDir(), "water_level_data.json");
if (!file.exists()) {
return null;
}
try {
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuilder jsonBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
jsonBuilder.append(line);
}
bufferedReader.close();
JSONObject jsonObject = new JSONObject(jsonBuilder.toString());
Date date;
try {
date = sdf.parse(jsonObject.getString("date"));
} catch (ParseException e) {
e.printStackTrace();
date = new Date(); // Default to current date if parsing fails
}
double waterLevel = jsonObject.optDouble("water_level", 0.0);
WaterLevelData data = new WaterLevelData(date, waterLevel, true);
data.setSurfaceArea(jsonObject.optDouble("surface_area", 0.0));
data.setReservoirStorage(jsonObject.optInt("reservoir_storage", 0));
data.setConservationStorage(jsonObject.optInt("conservation_storage", 0));
data.setPercentFull(jsonObject.optDouble("percent_full", 0.0));
data.setConservationCapacity(jsonObject.optInt("conservation_capacity", 0));
data.setDeadPoolCapacity(jsonObject.optInt("dead_pool_capacity", 0));
return data;
} catch (Exception e) {
e.printStackTrace();
Log.e("WaterLevelData", "Failed to load data", e);
}
return null; // Return null if loading fails
}
// Clear cached data
public static void clearCache(Context context) {
File file = new File(context.getFilesDir(), "water_level_data.json");
if (file.exists()) {
file.delete();
}
}
// Force data fetch
public static void forceDataFetch(Context context) {
new Thread(() -> {
String jsonResponse = getJsonResponseFromApi();
if (jsonResponse != null) {
WaterLevelData data = _loadWaterLevelData(jsonResponse);
if (data != null) {
data.saveWaterLevelData(context);
}
}
}).start();
}
}

View File

@ -0,0 +1,382 @@
package edu.utsa.cs3443.lakewatch.model;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
/**
* Represents the weather data for a specified location. This class holds various weather attributes
* such as temperature, wind speed, wind direction, and precipitation amounts.
* It provides methods to load weather data from a remote API and populate its fields accordingly.
*
* The weather data includes:
* - Weather description
* - High and low temperatures
* - Wind speed and gust
* - Wind direction
* - Chance of precipitation
* - Amount of precipitation
*
* @author Ethan Grams - bib016
*/
public class WeatherData {
private String weatherDescription;
private int tempHigh;
private int tempLow;
private String windSpeed;
private double windGust;
private String windDirection;
private int chancePrecipitation;
private double amountPrecipitation;
private static final String CACHE_FILE_NAME = "weather_data.json";
// Constructor with all fields
public WeatherData(String weatherDescription, int tempHigh, int tempLow, String windSpeed, double windGust, String windDirection, int chancePrecipitation, double amountPrecipitation) {
this.weatherDescription = weatherDescription;
this.tempHigh = tempHigh;
this.tempLow = tempLow;
this.windSpeed = windSpeed;
this.windGust = windGust;
this.windDirection = windDirection;
this.chancePrecipitation = chancePrecipitation;
this.amountPrecipitation = amountPrecipitation;
}
// Default Constructor
public WeatherData() {}
/**
* Loads weather data from the weather.gov API and creates a WeatherData object.
* This method fetches both hourly and general weather data for a specified weather station,
* populating the fields of the WeatherData object with the retrieved information.
*
* @return a WeatherData object populated with the fetched weather data
* @throws IOException if there is an error during the network connection or data retrieval
*/
public WeatherData loadWeatherData() throws IOException {
WeatherData weatherData = new WeatherData();
String hourlyUrl = "https://api.weather.gov/gridpoints/EWX/136,74/forecast/hourly";
String generalUrl = "https://api.weather.gov/gridpoints/EWX/136,74";
// Fetch hourly weather data
fetchHourlyWeatherData(hourlyUrl, weatherData);
// Fetch general weather data
fetchGeneralWeatherData(generalUrl, weatherData);
return weatherData;
}
/**
* Fetches and processes hourly weather data for a specified weather station.
* Establishes a connection to the given URL, retrieves the weather data in JSON format,
* and extracts the necessary values to populate the provided WeatherData object.
* Handles weather description, wind speed and direction, and chance of precipitation.
*
* @param url the URL to fetch weather data from
* @param weatherData the WeatherData object to populate with the fetched data
* @throws IOException if there is an error during the network connection or data retrieval
*/
private void fetchHourlyWeatherData(String url, WeatherData weatherData) throws IOException {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
try {
setupConnection(urlConnection);
int responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
String response = readStream(urlConnection.getInputStream());
JSONObject jsonResponse = new JSONObject(response);
JSONArray periods = jsonResponse.getJSONObject("properties").getJSONArray("periods");
JSONObject firstPeriod = periods.getJSONObject(0);
weatherData.setWeatherDescription(firstPeriod.getString("shortForecast"));
weatherData.setWindSpeed(firstPeriod.getString("windSpeed"));
weatherData.setWindDirection(firstPeriod.getString("windDirection"));
weatherData.setChancePrecipitation(firstPeriod.getJSONObject("probabilityOfPrecipitation").optInt("value", 0));
} else {
throw new IOException("HTTP error code: " + responseCode);
}
} catch (JSONException e) {
throw new RuntimeException(e);
} finally {
urlConnection.disconnect();
}
}
/**
* Fetches and processes general weather data for a specified weather station.
* Establishes a connection to the given URL, retrieves the weather data in JSON format,
* and extracts the necessary values to populate the provided WeatherData object.
* Handles high and low temperatures, wind gusts, and precipitation amounts,
* converting units as necessary.
*
* @param url the URL to fetch weather data from
* @param weatherData the WeatherData object to populate with the fetched data
* @throws IOException if there is an error during the network connection or data retrieval
*/
private void fetchGeneralWeatherData(String url, WeatherData weatherData) throws IOException {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
try {
setupConnection(urlConnection);
int responseCode = urlConnection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
String response = readStream(urlConnection.getInputStream());
JSONObject jsonResponse = new JSONObject(response);
JSONObject properties = jsonResponse.getJSONObject("properties");
weatherData.setTempHigh(convertToFahrenheit(getMaxTemp(properties.getJSONObject("maxTemperature").getJSONArray("values"))));
weatherData.setTempLow(convertToFahrenheit(getMinTemp(properties.getJSONObject("minTemperature").getJSONArray("values"))));
weatherData.setWindGust(convertKmhToMph(properties.getJSONObject("windGust").getJSONArray("values").getJSONObject(0).optInt("value")));
weatherData.setAmountPrecipitation(convertMmToInches(sumPrecipitation(properties.getJSONObject("quantitativePrecipitation").getJSONArray("values"), 4)));
} else {
throw new IOException("HTTP error code: " + responseCode);
}
} catch (JSONException e) {
throw new RuntimeException(e);
} finally {
urlConnection.disconnect();
}
}
/**
* Configures the given HttpURLConnection with the necessary request properties.
* Sets the request method to GET, and specifies that the response should be in JSON format.
* Also sets the User-Agent to mimic a modern web browser.
*
* @param connection the HttpURLConnection to be configured
* @throws ProtocolException if there is an error in the underlying protocol
*/
private void setupConnection(HttpURLConnection connection) throws ProtocolException {
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
}
// Reads in data and returns it in one String
private String readStream(InputStream in) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
out.append(line);
}
reader.close();
return out.toString();
}
// Takes two high temperatures within a 24 hour period and returns the maximum
private int getMaxTemp(JSONArray values) throws JSONException {
return Math.max(values.getJSONObject(0).optInt("value", 0), values.getJSONObject(1).optInt("value", 0));
}
// Returns the current low temperature
private int getMinTemp(JSONArray values) throws JSONException {
return values.getJSONObject(0).optInt("value", 0);
}
// Sums precipitation over a 24 hour period
private double sumPrecipitation(JSONArray values, int count) throws JSONException {
double sum = 0.0;
for (int i = 0; i < count; i++) {
sum += values.getJSONObject(i).optInt("value", 0);
}
return sum;
}
// Converts Celsius to Fahrenheit
private int convertToFahrenheit(int celsius) {
return (int) (celsius * (9.0 / 5.0)) + 32;
}
// Converts Kilometers Per Hour to Miles Per Hour
private double convertKmhToMph(double kmh) {
return kmh * 0.621371;
}
// Converts millimeters to inches
private double convertMmToInches(double mm) {
return mm * 0.0393701;
}
// Save the data to a JSON file
public void saveWeatherData(Context context) {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("weather_description", weatherDescription);
jsonObject.put("temp_high", tempHigh);
jsonObject.put("temp_low", tempLow);
jsonObject.put("wind_speed", windSpeed);
jsonObject.put("wind_gust", windGust);
jsonObject.put("wind_direction", windDirection);
jsonObject.put("chance_precipitation", chancePrecipitation);
jsonObject.put("amount_precipitation", amountPrecipitation);
File file = new File(context.getFilesDir(), "weather_data.json");
FileWriter fileWriter = new FileWriter(file);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write(jsonObject.toString());
bufferedWriter.close();
} catch (Exception e) {
e.printStackTrace();
Log.e("WaterLevelData", "Failed to save data", e);
}
}
// Load the data from a JSON file
public static WeatherData loadSavedWeatherData(Context context) {
File file = new File(context.getFilesDir(), "weather_data.json");
if (!file.exists()) {
return null;
}
try {
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuilder jsonBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
jsonBuilder.append(line);
}
bufferedReader.close();
JSONObject jsonObject = new JSONObject(jsonBuilder.toString());
double waterLevel = jsonObject.optDouble("water_level", 0.0);
WeatherData weatherData = new WeatherData();
weatherData.setWeatherDescription(jsonObject.optString("weather_description", ""));
weatherData.setTempHigh(jsonObject.optInt("temp_high", 0));
weatherData.setTempLow(jsonObject.optInt("temp_low", 0));
weatherData.setWindSpeed(jsonObject.optString("wind_speed", ""));
weatherData.setWindGust(jsonObject.optDouble("wind_gust", 0.0));
weatherData.setWindDirection(jsonObject.optString("wind_direction", ""));
weatherData.setChancePrecipitation(jsonObject.optInt("chance_precipitation", 0));
weatherData.setAmountPrecipitation(jsonObject.optDouble("amount_precipitation", 0.0));
return weatherData;
} catch (Exception e) {
e.printStackTrace();
Log.e("WeatherData", "Failed to load data", e);
}
return null; // Return null if loading fails
}
/**
* Wipe the cache file on disk
*
* @param context the android context used to look up the cache directory
* @throws IOException If the file cannot be deleted and it exists
*/
public static void wipeCache(Context context) throws IOException {
File file = new File(context.getCacheDir(), WeatherData.CACHE_FILE_NAME);
if (file.exists()) {
file.delete();
}
}
@NonNull
@Override
public String toString() {
return "WeatherData{" +
"weatherDescription='" + weatherDescription + '\'' +
", tempHigh=" + tempHigh +
", tempLow=" + tempLow +
", windSpeed='" + windSpeed + '\'' +
", windGust=" + windGust +
", windDirection='" + windDirection + '\'' +
", chancePrecipitation=" + chancePrecipitation +
", amountPrecipitation=" + amountPrecipitation +
'}';
}
// Getters and Setters
public String getWeatherDescription() {
return weatherDescription;
}
public void setWeatherDescription(String weatherDescription) {
this.weatherDescription = weatherDescription;
}
public int getTempHigh() {
return tempHigh;
}
public void setTempHigh(int tempHigh) {
this.tempHigh = tempHigh;
}
public int getTempLow() {
return tempLow;
}
public void setTempLow(int tempLow) {
this.tempLow = tempLow;
}
public String getWindSpeed() {
return windSpeed;
}
public void setWindSpeed(String windSpeed) {
this.windSpeed = windSpeed;
}
public double getWindGust() {
return windGust;
}
public void setWindGust(double windGust) {
this.windGust = windGust;
}
public String getWindDirection() {
return windDirection;
}
public void setWindDirection(String windDirection) {
this.windDirection = windDirection;
}
public int getChancePrecipitation() {
return chancePrecipitation;
}
public void setChancePrecipitation(int chancePrecipitation) {
this.chancePrecipitation = chancePrecipitation;
}
public double getAmountPrecipitation() {
return amountPrecipitation;
}
public void setAmountPrecipitation(double amountPrecipitation) {
this.amountPrecipitation = amountPrecipitation;
}
}

View File

@ -0,0 +1,29 @@
package edu.utsa.cs3443.lakewatch.model;
import android.app.Application;
import android.content.SharedPreferences;
import android.graphics.Color;
import androidx.appcompat.app.AppCompatDelegate;
/**
* This class is referenced on line 6
* of the AndroidManifest.XML to implement
* dark mode universally within the app's views
* @author Gavin Diab
*/
public class userSettings extends Application {
@Override
public void onCreate() {
super.onCreate();
// Load the theme setting from SharedPreferences
SharedPreferences sharedPreferences = getSharedPreferences("sharedPrefs", MODE_PRIVATE);
boolean isDarkModeOn = sharedPreferences.getBoolean("isDarkModeOn", false);
// Apply the theme setting
if (isDarkModeOn) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
}
}

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:pathData="M8.5,7.092l9.565,2.639 5.309,-3.731 1.847,0.455 -5.259,3.742 28.985,8.053 -2.121,7.882 -29.093,-8.031c-11.271,-3.399 -9.216,-11.009 -9.216,-11.009"
android:fillColor="?attr/colorControlNormal"/>
<path
android:pathData="M33.957,27.258c-0.035,-0.658 -0.16,-1.285 -0.375,-1.877l13.281,3.697 -0.426,1.639 -12.48,-3.459zM21.891,23.926c0.358,-0.521 0.782,-0.991 1.264,-1.398l-22.155,-6.12v1.763l20.891,5.755zM27.377,29.762c1.191,0 2.158,-0.969 2.158,-2.16 0,-1.195 -0.967,-2.162 -2.158,-2.162 -1.195,0 -2.161,0.967 -2.161,2.162 0,1.191 0.966,2.16 2.161,2.16zM49,43c-1.051,0 -2.051,-0.238 -2.943,-0.648 -0.928,-0.42 -1.963,-0.672 -3.047,-0.672 -1.08,0 -2.121,0.252 -3.035,0.672 -0.905,0.41 -1.903,0.648 -2.955,0.648 -1.051,0 -2.053,-0.238 -2.955,-0.648 -0.92,-0.42 -1.953,-0.672 -3.035,-0.672 -1.086,0 -2.119,0.252 -3.045,0.672 -0.893,0.41 -1.905,0.648 -2.951,0.648 -1.045,0 -2.051,-0.238 -2.949,-0.648 -0.926,-0.42 -1.967,-0.672 -3.046,-0.672 -1.08,0 -2.12,0.252 -3.035,0.672 -0.898,0.41 -1.909,0.648 -2.956,0.648 -1.045,0 -2.051,-0.238 -2.955,-0.648 -0.916,-0.42 -1.956,-0.672 -3.036,-0.672 -1.079,0 -2.119,0.252 -3.04,0.672 -0.897,0.41 -1.909,0.648 -2.949,0.648l-0.068,-17.605 24.227,6.686c-1.67,-0.807 -2.83,-2.5 -2.83,-4.479 0,-2.754 2.227,-4.983 4.979,-4.983 2.744,0 4.977,2.229 4.977,4.983 0,2.752 -2.232,4.98 -4.977,4.98l-0.49,-0.047 22.078,6.088 0.036,4.377z"
android:fillColor="?attr/colorControlNormal"/>
</vector>

View File

@ -0,0 +1,81 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#f2fbb7"
android:strokeColor="#000000"/>
<path
android:pathData="m92.23,143.09h327.89a10,10 45,0 1,10 10v227.89a10,10 135,0 1,-10 10L92.23,390.98a10,10 45,0 1,-10 -10L82.23,153.09a10,10 135,0 1,10 -10z"
android:strokeLineJoin="round"
android:strokeWidth="2.10845"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M87.39,135.48L424.96,135.48a10,10 45,0 1,10 10v17.57a10,10 135,0 1,-10 10L87.39,173.05a10,10 45,0 1,-10 -10v-17.57a10,10 135,0 1,10 -10z"
android:strokeLineJoin="round"
android:strokeWidth="2.42674"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M107.23,142.67a16.44,26.44 0,1 0,32.89 0a16.44,26.44 0,1 0,-32.89 0z"
android:strokeLineJoin="round"
android:strokeWidth="2.11426"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M372.23,142.67a16.44,26.44 0,1 0,32.89 0a16.44,26.44 0,1 0,-32.89 0z"
android:strokeLineJoin="round"
android:strokeWidth="2.11426"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="m139.19,200h61.32a4,4 45,0 1,4 4v61.32a4,4 135,0 1,-4 4h-61.32a4,4 45,0 1,-4 -4v-61.32a4,4 135,0 1,4 -4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="m311.83,200h61.32a4,4 45,0 1,4 4v61.32a4,4 135,0 1,-4 4h-61.32a4,4 45,0 1,-4 -4v-61.32a4,4 135,0 1,4 -4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="m225.51,286.32h61.32a4,4 45,0 1,4 4v61.32a4,4 135,0 1,-4 4h-61.32a4,4 45,0 1,-4 -4v-61.32a4,4 135,0 1,4 -4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="m225.51,200h61.32a4,4 45,0 1,4 4v61.32a4,4 135,0 1,-4 4h-61.32a4,4 45,0 1,-4 -4v-61.32a4,4 135,0 1,4 -4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="m139.19,286.32h61.32a4,4 45,0 1,4 4v61.32a4,4 135,0 1,-4 4h-61.32a4,4 45,0 1,-4 -4v-61.32a4,4 135,0 1,4 -4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="m311.83,286.32h61.32a4,4 45,0 1,4 4v61.32a4,4 135,0 1,-4 4h-61.32a4,4 45,0 1,-4 -4v-61.32a4,4 135,0 1,4 -4z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#000000"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#72F18A"
android:strokeColor="#000000"/>
<path
android:pathData="m345.65,142.86 l-136.43,136.43 -42.83,-42.83 -44.94,44.94 88.09,88.09 0.35,-0.35 44.59,-44.59h0L390.9,188.11Z"
android:strokeWidth="2"
android:fillColor="#ffffff"
android:strokeColor="#000000"/>
</vector>

View File

@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#b7f8fb"
android:strokeColor="#000000"/>
<path
android:pathData="M147.58,131.93l232.62,0l0,232.62l-232.62,0z"
android:strokeLineJoin="round"
android:strokeWidth="7.96"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M132.15,147.8l232.62,0l0,232.62l-232.62,0z"
android:strokeLineJoin="round"
android:strokeWidth="7.96"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#cacaca"
android:strokeColor="#000000"/>
<path
android:pathData="m258.67,119.57q23.63,0 40.54,8.45 17.1,8.45 26.13,22.09 9.03,13.45 9.03,28.63 0,18.44 -6.92,30.16 -6.92,11.53 -17.29,19.6 -10.18,7.88 -20.56,15.56 -10.37,7.49 -17.48,17.68 -6.92,10.18 -6.92,26.13v13.26L232.73,301.13L232.73,285.95q0,-18.64 6.72,-30.93 6.92,-12.3 17.1,-20.94 10.18,-8.65 20.37,-15.95 10.18,-7.49 17.1,-16.14 6.92,-8.65 6.92,-21.33 0,-16.33 -13.06,-25.17 -13.06,-9.03 -32.47,-9.03 -13.64,0 -28.05,6.72 -14.41,6.53 -26.32,21.33l-23.06,-16.91q16.91,-19.79 37.08,-28.82 20.37,-9.22 43.61,-9.22zM249.83,341.29q11.14,0 18.64,7.49 7.49,7.49 7.49,18.06 0,10.76 -7.49,18.44 -7.49,7.49 -18.64,7.49 -10.76,0 -18.06,-7.49 -7.3,-7.69 -7.3,-18.44 0,-10.57 7.3,-18.06 7.3,-7.49 18.06,-7.49z"
android:strokeWidth="2"
android:fillColor="#ffffff"
android:strokeColor="#000000"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/circle_guess"
android:width="40dp"
android:height="40dp"
/>
</layer-list>

View File

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#d0b7fb"
android:strokeColor="#000000"/>
<path
android:pathData="M171.42,85 L213.79,169.85 256.17,254.69 298.55,169.85 340.93,85L256.17,85ZM256.17,257.66 L213.79,342.5 171.42,427.34L256.17,427.34 340.93,427.34l-42.38,-84.84z"
android:strokeLineJoin="round"
android:strokeWidth="6"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,25 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#bafbb7"
android:strokeColor="#000000"/>
<path
android:pathData="m159.28,190.03v227.07h65.16l0.41,-1.07L256.17,335.25l31.32,80.78 0.41,1.07h65.16L353.07,190.03Z"
android:strokeLineJoin="round"
android:strokeWidth="6.21387"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
<path
android:pathData="M256.17,192.14m-96.63,0a96.63,96.63 0,1 1,193.27 0a96.63,96.63 0,1 1,-193.27 0"
android:strokeLineJoin="round"
android:strokeWidth="6.73409"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="butt"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512.35dp"
android:height="512.35dp"
android:viewportWidth="512.35"
android:viewportHeight="512.35">
<path
android:pathData="M256.17,256.17m-255.17,0a255.17,255.17 0,1 1,510.35 0a255.17,255.17 0,1 1,-510.35 0"
android:strokeWidth="2"
android:fillColor="#ff7f7f"
android:strokeColor="#000000"/>
<path
android:pathData="M325.85,129.86L255.11,200.6L185.43,130.92L140.95,175.39l69.68,69.68l-69.04,69.04l44.47,44.47l69.04,-69.04l70.1,70.1l44.47,-44.47l-70.1,-70.1l70.74,-70.75z"
android:strokeWidth="2"
android:fillColor="#ffffff"
android:strokeColor="#000000"/>
</vector>

View File

@ -0,0 +1,59 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M32,41L32,44"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M25.636,38.364L23.515,40.485"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M23,32L20,32"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M25.636,25.636L23.515,23.515"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M32,23L32,20"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M38.364,25.636L40.485,23.515"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M41,32L44,32"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M38.364,38.364L40.485,40.485"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#ffa500"
android:strokeLineCap="round"/>
<path
android:pathData="M32,32m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:strokeWidth="2"
android:fillColor="#ffa500"
android:strokeColor="#ffa500"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="33.104dp"
android:height="24.037dp"
android:viewportWidth="33.104"
android:viewportHeight="24.037">
<path
android:pathData="M6.043,10.713A6,6 0,0 0,7.492 22.537L23.992,22.537a7.5,7.5 0,1 0,-1.755 -14.793l-2,0.543"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="?attr/colorControlNormal"
android:strokeColor="?attr/colorControlNormal"
android:strokeLineCap="round"/>
<path
android:pathData="M22.237,7.744a8.25,8.25 0,0 0,-16.194 2.97,7.142 7.142,0 0,0 0.7,2.073"
android:strokeLineJoin="round"
android:strokeWidth="3"
android:fillColor="?attr/colorControlNormal"
android:strokeColor="?attr/colorControlNormal"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="33dp"
android:height="37.5dp"
android:viewportWidth="33"
android:viewportHeight="37.5">
<path
android:pathData="M15.558,1.165 L5.109,12.661a1.623,1.623 0,0 0,1.2 2.714L8.063,15.375L2.902,20.536A1.591,1.591 0,0 0,4.027 23.25L6.375,23.25L1.13,29.543A1.653,1.653 0,0 0,2.402 32.25L14.25,32.25v2.25a2.25,2.25 0,0 0,4.5 0L18.75,32.25L30.598,32.25a1.653,1.653 0,0 0,1.273 -2.707L26.625,23.25h2.348a1.591,1.591 0,0 0,1.125 -2.714l-5.161,-5.161h1.751A1.625,1.625 0,0 0,28.313 13.75a1.6,1.6 0,0 0,-0.422 -1.09L17.442,1.165a1.277,1.277 0,0 0,-1.884 0Z"
android:strokeWidth="1.5"
android:fillColor="?attr/colorControlNormal"
android:strokeColor="?attr/colorControlNormal"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="360.5dp"
android:height="37.24dp"
android:viewportWidth="360.5"
android:viewportHeight="37.24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M360.14,23.7h0ZM60.38,20.31c21.45,0 25.6,3.39 60.02,3.39 35.15,0 37.67,-3.39 60.02,-3.39 21.45,0 25.6,3.39 60.02,3.39 35.15,0 37.67,-3.39 60.02,-3.39 21.45,0 25.24,3.32 59.66,3.39L360.14,16.93c-21.45,0 -25.6,-3.39 -60.02,-3.39 -35.15,0 -37.67,3.39 -60.02,3.39 -21.45,0 -25.6,-3.39 -60.02,-3.39 -35.15,0 -37.67,3.39 -60.02,3.39 -21.45,0 -25.6,-3.39 -60.02,-3.39 -35.15,0 -37.67,3.39 -60.02,3.39L0,23.7c34.25,0 39.11,-3.39 60.38,-3.39ZM300.48,27.08c-35.15,0 -37.67,3.39 -60.02,3.39 -21.45,0 -25.6,-3.39 -60.02,-3.39 -35.15,0 -37.85,3.39 -60.2,3.39S95.35,27.09 60.2,27.09 22.35,30.47 0,30.47v6.77c35.15,0 38.03,-3.39 60.2,-3.39 22.35,0 24.88,3.39 60.02,3.39s37.85,-3.39 60.2,-3.39c21.45,0 25.6,3.39 60.02,3.39 34.97,0 37.67,-3.39 60.02,-3.39 21.45,0 25.6,3.39 60.02,3.39L360.49,30.47c-22.35,0 -24.88,-3.39 -60.02,-3.39ZM60.38,6.77c21.45,0 25.6,3.39 60.02,3.39 35.15,0 37.67,-3.39 60.02,-3.39 21.45,0 25.6,3.39 60.02,3.39 35.15,0 37.67,-3.39 60.02,-3.39 21.45,0 25.24,3.32 59.66,3.39L360.14,3.39c-21.45,0 -25.6,-3.39 -60.02,-3.39 -35.15,0 -37.67,3.39 -60.02,3.39 -21.45,0 -25.6,-3.39 -60.02,-3.39 -35.15,0 -37.67,3.39 -60.02,3.39 -21.45,0 -25.6,-3.39 -60.02,-3.39C24.88,0 22.35,3.39 0,3.39v6.77C34.25,10.16 39.11,6.77 60.38,6.77Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="26.652dp"
android:height="17.46dp"
android:viewportWidth="26.652"
android:viewportHeight="17.46">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M0,12.36A5.1,5.1 0,0 1,3.972 7.392a6.091,6.091 0,0 1,6 -4.8,5.942 5.942,0 0,1 3.792,1.308 5.88,5.88 0,0 1,2.16 3.36h0.324a5.1,5.1 0,0 1,5.112 5.088,5.121 5.121,0 0,1 -5.112,5.112L5.124,17.46a4.836,4.836 0,0 1,-1.98 -0.408,5.328 5.328,0 0,1 -1.644,-1.068 5.333,5.333 0,0 1,-1.092 -1.632,4.933 4.933,0 0,1 -0.408,-1.992ZM1.74,12.36a3.228,3.228 0,0 0,0.984 2.352,3.27 3.27,0 0,0 2.388,0.984L16.248,15.696a3.262,3.262 0,0 0,2.388 -0.984,3.212 3.212,0 0,0 1,-2.352 3.148,3.148 0,0 0,-1 -2.352,3.289 3.289,0 0,0 -2.388,-0.984L14.58,9.024a0.159,0.159 0,0 1,-0.18 -0.18l-0.084,-0.588a4.363,4.363 0,0 0,-4.368 -3.936,4.267 4.267,0 0,0 -2.952,1.128 4.169,4.169 0,0 0,-1.416 2.808l-0.084,0.5c0,0.12 -0.06,0.18 -0.192,0.18l-0.54,0.084A3.228,3.228 0,0 0,2.592 10.092a3.367,3.367 0,0 0,-0.852 2.268ZM12.36,1.896c-0.12,0.108 -0.1,0.192 0.084,0.252a10.7,10.7 0,0 1,1.3 0.66c0.132,0.036 0.228,0.024 0.264,-0.036a3.7,3.7 0,0 1,6.252 2.34l0.108,0.768h1.7a2.747,2.747 0,0 1,2.016 0.84,2.77 2.77,0 0,1 0.1,3.876 2.83,2.83 0,0 1,-1.836 0.924c-0.12,0 -0.18,0.06 -0.18,0.192v1.356c0,0.132 0.06,0.192 0.18,0.192a4.577,4.577 0,0 0,4.308 -4.548,4.589 4.589,0 0,0 -4.584,-4.584h-0.18a5.588,5.588 0,0 0,-5.34 -4.128,5.27 5.27,0 0,0 -4.188,1.9Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="360dp"
android:height="1dp"
android:viewportWidth="360"
android:viewportHeight="1">
<path
android:pathData="M0,0.5L360,0.5"
android:strokeWidth="1"
android:fillColor="?attr/colorControlNormal"
android:strokeColor="#707070"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="301.68dp"
android:height="82.76dp"
android:viewportWidth="301.68"
android:viewportHeight="82.76">
<path
android:strokeWidth="1"
android:pathData="M10.3,0.5L291.38,0.5A9.8,9.8 0,0 1,301.18 10.3L301.18,72.46A9.8,9.8 0,0 1,291.38 82.26L10.3,82.26A9.8,9.8 0,0 1,0.5 72.46L0.5,10.3A9.8,9.8 0,0 1,10.3 0.5z"
android:fillColor="#fff"
android:strokeColor="#231f20"/>
<path
android:strokeWidth="1"
android:pathData="M33.55,41.38h174.32H33.55Z"
android:fillColor="#00000000"
android:strokeColor="#231f20"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 KiB

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:fillColor="#FF000000"
android:pathData="M20.33,2a9.72,9.72 0,0 0,-6.12 2.17A5,5 0,0 0,6 7.59a7.08,7.08 0,0 0,-4 6.23v0.08H2v0a7,7 0,0 0,0.56 2.74,2.15 2.15,0 0,0 0.11,0.22A7.11,7.11 0,0 0,9.1 21H21.66a7.05,7.05 0,0 0,6 -3.32A9.38,9.38 0,0 0,30 11.5,9.6 9.6,0 0,0 20.33,2ZM11,5a3,3 0,0 1,1.8 0.6,9.58 9.58,0 0,0 -1.13,1.69 7.43,7.43 0,0 0,-2.48 -0.43,7.93 7.93,0 0,0 -1,0.07A3,3 0,0 1,11 5ZM26.11,16.42a0.71,0.71 0,0 0,-0.1 0.14A5.08,5.08 0,0 1,21.66 19H9.1a5.13,5.13 0,0 1,-4.62 -2.95l-0.05,-0.11 0,-0.1A4.74,4.74 0,0 1,4 14v-0.14a5.15,5.15 0,0 1,5.19 -5,5.27 5.27,0 0,1 2.52,0.64 1,1 0,0 0,0.83 0.05A1,1 0,0 0,13.13 9a7.68,7.68 0,0 1,7.2 -5A7.59,7.59 0,0 1,28 11.5,7.4 7.4,0 0,1 26.11,16.42Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M23.55,6.17a1,1 0,0 0,-1.1 1.66A4.59,4.59 0,0 1,24 12.76,1 1,0 0,0 24.76,14L25,14a1,1 0,0 0,1 -0.76A6.51,6.51 0,0 0,23.55 6.17Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M11,23a1,1 0,0 0,-1 1v5a1,1 0,0 0,2 0V24A1,1 0,0 0,11 23Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M16,23a1,1 0,0 0,-1 1v2.5a1,1 0,0 0,2 0V24A1,1 0,0 0,16 23Z"/>
<path
android:fillColor="#FF000000"
android:pathData="M21,23a1,1 0,0 0,-1 1v3.75a1,1 0,0 0,2 0V24A1,1 0,0 0,21 23Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="1920"
android:viewportHeight="1920">
<path
android:pathData="m1739.3,1293.4 l-105.8,180.8 -240.2,-80.2 -24.5,22.3c-69.9,63.6 -150.2,109.7 -238.6,136.8l-32.1,9.9 -49.5,244.1L835.6,1807.1l-49.5,-244.2 -32.1,-9.9c-88.4,-27.1 -168.7,-73.2 -238.6,-136.8l-24.5,-22.3 -240.2,80.2 -105.8,-180.8 189.7,-164.4 -7.5,-33c-10.4,-45.7 -15.6,-91.5 -15.6,-135.9 0,-44.4 5.2,-90.1 15.6,-135.9l7.5,-33 -189.7,-164.4 105.8,-180.8 240.2,80.1 24.5,-22.3c69.9,-63.6 150.2,-109.7 238.6,-136.9l32.1,-9.8 49.5,-244.1h213l49.5,244.2 32.1,9.8c88.4,27.2 168.7,73.2 238.6,136.9l24.5,22.3 240.2,-80.2 105.8,180.8 -189.7,164.4 7.5,33c10.4,45.7 15.6,91.5 15.6,135.9 0,44.4 -5.2,90.1 -15.6,135.9l-7.5,33 189.7,164.6ZM1685.6,960c0,-41.8 -3.8,-84.5 -11.6,-127.3l210.2,-182.1 -199.5,-340.9 -265.2,88.4c-67,-55.6 -143.3,-99.4 -223.9,-128.4L1141,0L743.2,0l-54.7,269.7c-81.4,29.1 -156.4,72.3 -224,128.4L199.5,309.8 0,650.7l210.1,182.1c-7.7,42.8 -11.5,85.5 -11.5,127.3 0,41.8 3.8,84.5 11.5,127.2L0,1269.4 199.5,1610.2l265.2,-88.5c67,55.7 143.3,99.4 223.9,128.5l54.7,269.8h397.8l54.7,-269.7c81.3,-29.3 156.4,-72.3 223.9,-128.5l265.2,88.5 199.5,-340.9 -210.2,-182.2c7.8,-42.8 11.6,-85.5 11.6,-127.3ZM942.1,564.7C724.1,564.7 546.8,742 546.8,960c0,218 177.3,395.3 395.3,395.3 218,0 395.3,-177.3 395.3,-395.3 0,-218 -177.3,-395.3 -395.3,-395.3m0,677.6c-155.6,0 -282.4,-126.7 -282.4,-282.4s126.7,-282.4 282.4,-282.4S1224.4,804.4 1224.4,960s-126.7,282.4 -282.4,282.4"
android:fillColor="?attr/colorControlNormal"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!-- No fill color -->
<solid android:color="@android:color/transparent"/>
<!-- Border style -->
<stroke android:width="1dp" android:color="#000000"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF" /> <!-- Cell background color -->
<stroke android:width="1dp" android:color="#000000" /> <!-- Border color and width -->
</shape>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16,13.385C16,14.609 15.526,15.783 14.682,16.648C14.203,17.14 13.627,17.509 13,17.736M19,13.692C19,7.115 12,2 12,2C12,2 5,7.115 5,13.692C5,15.63 5.738,17.489 7.05,18.86C8.363,20.23 10.144,20.999 12,20.999C13.857,20.999 15.637,20.23 16.95,18.859C18.263,17.489 19,15.63 19,13.692Z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="?attr/colorControlNormal"
android:strokeLineCap="round"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="196.27dp"
android:height="25.32dp"
android:viewportWidth="196.27"
android:viewportHeight="25.32">
<path
android:strokeWidth="1"
android:pathData="M10.3,0.5L185.97,0.5A9.8,9.8 0,0 1,195.77 10.3L195.77,15.02A9.8,9.8 0,0 1,185.97 24.82L10.3,24.82A9.8,9.8 0,0 1,0.5 15.02L0.5,10.3A9.8,9.8 0,0 1,10.3 0.5z"
android:fillColor="?attr/colorControlNormal"
android:strokeColor="?attr/colorControlNormal"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M55.13,261.43c4.61,-4.72 9.61,-9.05 14.93,-12.96C54.27,232.77 44.49,211.05 44.49,187.03c0,-47.84 38.79,-86.63 86.63,-86.63c32.81,0 61.35,18.25 76.05,45.15c11.37,-7.76 23.87,-13.95 37.21,-18.3l0.64,-1.79c0.48,-1.32 0.34,-2.79 -0.35,-4c-0.71,-1.22 -1.91,-2.07 -3.29,-2.31l-32.66,-5.87c-1.96,-0.35 -3.5,-1.89 -3.86,-3.86l-5.87,-32.66c-0.25,-1.38 -1.09,-2.59 -2.31,-3.29c-1.21,-0.71 -2.67,-0.85 -4,-0.36l-31.22,11.21c-1.87,0.68 -3.97,0.12 -5.26,-1.4l-21.44,-25.33c-0.9,-1.07 -2.25,-1.69 -3.64,-1.69c-1.41,0 -2.74,0.62 -3.65,1.69L106.04,82.93c-1.29,1.53 -3.39,2.08 -5.27,1.42L69.55,73.13c-1.32,-0.48 -2.79,-0.35 -4.01,0.35c-1.22,0.7 -2.05,1.91 -2.31,3.29l-5.86,32.66c-0.36,1.97 -1.9,3.5 -3.86,3.86l-32.66,5.86c-1.39,0.25 -2.58,1.1 -3.29,2.31c-0.71,1.22 -0.84,2.68 -0.36,4.01l11.22,31.22c0.67,1.88 0.12,3.97 -1.41,5.27L1.69,183.39C0.62,184.29 0,185.63 0,187.03c0,1.41 0.62,2.74 1.69,3.65l25.33,21.44c1.53,1.29 2.08,3.39 1.41,5.27l-11.22,31.22c-0.48,1.33 -0.34,2.79 0.36,4.01c0.71,1.21 1.9,2.05 3.29,2.31l32.66,5.86C54.1,260.88 54.64,261.13 55.13,261.43z"
android:fillColor="?attr/colorControlNormal"/>
<path
android:pathData="M483.62,290.7c-14.77,-14.78 -34.42,-24.74 -56.25,-27.51c-1.9,-34.17 -16.4,-65.07 -39.1,-87.75c-24.35,-24.37 -58.13,-39.49 -95.31,-39.48c-34.83,-0.01 -66.68,13.26 -90.57,34.97c-20.55,18.65 -35.28,43.64 -41.23,71.84c-5.78,-0.96 -11.69,-1.5 -17.73,-1.5c-29.62,-0.01 -56.55,12.03 -75.95,31.46c-19.42,19.39 -31.46,46.33 -31.46,75.94c0,29.62 12.04,56.56 31.46,75.95c19.4,19.42 46.33,31.46 75.95,31.46H415.12c26.72,0 51.01,-10.86 68.5,-28.38c17.51,-17.49 28.39,-41.79 28.38,-68.5C512.01,332.49 501.14,308.18 483.62,290.7zM465.75,409.83c-13,12.98 -30.82,20.97 -50.63,20.98H143.44c-22.72,-0.01 -43.18,-9.17 -58.08,-24.06c-14.89,-14.9 -24.05,-35.35 -24.06,-58.08c0.01,-22.72 9.17,-43.18 24.06,-58.08c14.9,-14.88 35.36,-24.05 58.08,-24.06c8.48,0 16.62,1.28 24.29,3.66l14.73,4.55l1.58,-15.34c2.82,-27.57 15.88,-52.09 35.34,-69.77c19.48,-17.66 45.22,-28.4 73.59,-28.4c30.27,0 57.58,12.23 77.44,32.08c19.85,19.85 32.07,47.16 32.08,77.44c0,1.07 -0.04,2.31 -0.1,3.77l-0.48,13.59l13.42,-0.52c19.73,0.07 37.47,8.04 50.43,20.97c12.98,13 20.96,30.82 20.98,50.63C486.71,379.02 478.73,396.84 465.75,409.83z"
android:fillColor="?attr/colorControlNormal"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="82.8dp" android:viewportHeight="82.76" android:viewportWidth="301.68" android:width="301.8258dp">
<path android:fillColor="#fff" android:pathData="M10.3,0.5L291.38,0.5A9.8,9.8 0,0 1,301.18 10.3L301.18,72.46A9.8,9.8 0,0 1,291.38 82.26L10.3,82.26A9.8,9.8 0,0 1,0.5 72.46L0.5,10.3A9.8,9.8 0,0 1,10.3 0.5z" android:strokeColor="#231f20" android:strokeWidth="1"/>
<path android:fillColor="#fff" android:pathData="M242.39,41.38m-20.27,0a20.27,20.27 0,1 1,40.54 0a20.27,20.27 0,1 1,-40.54 0" android:strokeColor="#231f20" android:strokeWidth="1"/>
<path android:fillColor="#00000000" android:pathData="M33.55,41.38h174.32H33.55Z" android:strokeColor="#231f20" android:strokeWidth="1"/>
</vector>

View File

@ -0,0 +1,285 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BoatRampActivity">
<TextView
android:id="@+id/rampName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Ramp Name"
android:textColor="@color/textColor"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/rampImage"
android:layout_width="0dp"
android:layout_height="270dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:scaleType="fitXY"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rampName"
app:srcCompat="@mipmap/ic_launcher" />
<ScrollView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rampImage">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:id="@+id/statusCard"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
app:cardBackgroundColor="#E9E9E9"
app:cardCornerRadius="3000dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#00FFFFFF"
android:orientation="horizontal">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:fillViewport="true">
<TextView
android:id="@+id/statusText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FFFFFF"
android:gravity="center"
android:text="Unknown"
android:textColor="@color/rampStatusText"
android:textSize="24sp" />
</HorizontalScrollView>
<ImageView
android:id="@+id/statusIcon"
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/circle_guess" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/openTimes"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
app:cardCornerRadius="3000dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#00FFFFFF"
android:orientation="horizontal">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:fillViewport="true">
<TextView
android:id="@+id/openTimesText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#00FFFFFF"
android:gravity="center"
android:text="Unknown"
android:textAlignment="gravity"
android:textColor="@color/rampStatusText"
android:textSize="24sp" />
</HorizontalScrollView>
<ImageView
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/circle_hour_glass" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
app:cardCornerRadius="3000dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#00FFFFFF"
android:orientation="horizontal">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:fillViewport="true">
<TextView
android:id="@+id/addressText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#00FFFFFF"
android:gravity="center"
android:text="Unknown"
android:textAlignment="gravity"
android:textColor="@color/rampStatusText"
android:textSize="24sp" />
</HorizontalScrollView>
<ImageView
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/circle_copy" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/operator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
app:cardCornerRadius="3000dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#00FFFFFF"
android:orientation="horizontal">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:fillViewport="true">
<TextView
android:id="@+id/operatorText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#00FFFFFF"
android:gravity="center"
android:text="Unknown"
android:textAlignment="gravity"
android:textColor="@color/rampStatusText"
android:textSize="24sp" />
</HorizontalScrollView>
<ImageView
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/circle_ops" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/lastUpdated"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
app:cardCornerRadius="3000dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#00FFFFFF"
android:orientation="horizontal">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:layout_marginRight="3dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:fillViewport="true">
<TextView
android:id="@+id/lastUpdatedText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#00FFFFFF"
android:gravity="center"
android:text="Unknown"
android:textAlignment="gravity"
android:textColor="@color/rampStatusText"
android:textSize="24sp" />
</HorizontalScrollView>
<ImageView
android:layout_width="120dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/calendar" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,264 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:outlineProvider="none"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="LAKEWATCH"
android:textSize="34sp"
android:textStyle="bold|italic"
android:textColor="@color/textColor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.125" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="CANYON LAKE"
android:textSize="34sp"
android:textColor="@color/textColor"
android:textStyle="bold|italic"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.177" />
<ImageView
android:id="@+id/imageView"
android:layout_width="45sp"
android:layout_height="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.136"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.281"
app:srcCompat="@drawable/icon_fa_solid_tree" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="45sp"
android:layout_height="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.89"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.281"
app:srcCompat="@drawable/icon_fa_solid_tree" />
<ImageView
android:id="@+id/imageView3"
android:layout_width="400sp"
android:layout_height="wrap_content"
android:scaleY="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.454"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.318"
app:srcCompat="@drawable/line_1" />
<ImageView
android:id="@+id/imageView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleX="1.1"
android:scaleY="1.1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.49"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.981"
app:srcCompat="@drawable/icon_material_sharp_water" />
<ImageView
android:id="@+id/imageView5"
android:layout_width="40sp"
android:layout_height="45sp"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.706"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.023"
app:srcCompat="@drawable/icon_akar_cloud" />
<ImageView
android:id="@+id/imageView6"
android:layout_width="40sp"
android:layout_height="45sp"
android:layout_marginBottom="220sp"
android:rotationY="177"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.148"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.069"
app:srcCompat="@drawable/icon_akar_cloud" />
<ImageView
android:id="@+id/imageView7"
android:layout_width="35sp"
android:layout_height="45sp"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.252"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.018"
app:srcCompat="@drawable/icon_weather_cloudy" />
<ImageView
android:id="@+id/imageView8"
android:layout_width="35sp"
android:layout_height="45sp"
android:rotationY="182"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.617"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.051"
app:srcCompat="@drawable/icon_weather_cloudy" />
<Button
style="@style/BaseButtonStyle"
android:id="@+id/button"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="WEATHER"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.483"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.441"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<Button
android:id="@+id/button2"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="WATER STATUS"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/button"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/button"
app:layout_constraintTop_toBottomOf="@+id/button"
app:layout_constraintVertical_bias="0.019"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<Button
android:id="@+id/button3"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="BOAT RAMPS"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="@+id/button2"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/button2"
app:layout_constraintTop_toBottomOf="@+id/button2"
app:layout_constraintVertical_bias="0.032"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<Button
android:id="@+id/settingsButton"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="SETTINGS"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/imageView4"
app:layout_constraintEnd_toEndOf="@+id/button3"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/button3"
app:layout_constraintTop_toBottomOf="@+id/button3"
app:layout_constraintVertical_bias="0.049"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<ImageView
android:id="@+id/imageView9"
android:layout_width="75sp"
android:layout_height="100sp"
android:layout_marginTop="540dp"
android:rotationY="177"
app:layout_constraintBottom_toBottomOf="@+id/button3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.235"
app:layout_constraintStart_toEndOf="@+id/button3"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/boat_ramp_svgrepo_com" />
<ImageView
android:id="@+id/imageView11"
android:layout_width="75sp"
android:layout_height="100sp"
app:layout_constraintBottom_toBottomOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.922"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/weather_symbol_4_svgrepo_com" />
<ImageView
android:id="@+id/imageView12"
android:layout_width="75sp"
android:layout_height="100sp"
app:layout_constraintBottom_toBottomOf="@+id/button2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.922"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView11"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/water_drop_svgrepo_com" />
<ImageView
android:id="@+id/imageView14"
android:layout_width="75sp"
android:layout_height="100sp"
app:layout_constraintBottom_toBottomOf="@+id/settingsButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.235"
app:layout_constraintStart_toEndOf="@+id/settingsButton"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/settings_2_svgrepo_com" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainRampMenuActivity">
<TextView
android:id="@+id/rampsOpen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="\?/\? Ramps Open"
android:textColor="@color/textColor"
android:textSize="34sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:id="@+id/rampListingScrollView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rampsOpen"
app:layout_constraintVertical_bias="0.0">
<LinearLayout
android:id="@+id/rampListing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SettingsActivity">
<ImageView
android:id="@+id/imageView3"
android:layout_width="400sp"
android:layout_height="wrap_content"
android:scaleY="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.454"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.318"
app:srcCompat="@drawable/line_1" />
<ImageView
android:id="@+id/imageView"
android:layout_width="45sp"
android:layout_height="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.136"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.281"
app:srcCompat="@drawable/icon_fa_solid_tree" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="45sp"
android:layout_height="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.89"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.281"
app:srcCompat="@drawable/icon_fa_solid_tree" />
<ImageView
android:id="@+id/imageView6"
android:layout_width="40sp"
android:layout_height="45sp"
android:layout_marginTop="52dp"
android:rotationY="177"
app:layout_constraintBottom_toTopOf="@+id/imageView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.11"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/icon_akar_cloud" />
<ImageView
android:id="@+id/imageView7"
android:layout_width="35sp"
android:layout_height="45sp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="@+id/imageView6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.252"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/icon_weather_cloudy" />
<ImageView
android:id="@+id/imageView8"
android:layout_width="35sp"
android:layout_height="45sp"
android:rotationY="182"
app:layout_constraintBottom_toBottomOf="@+id/imageView6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.651"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView5"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@drawable/icon_weather_cloudy" />
<ImageView
android:id="@+id/imageView5"
android:layout_width="40sp"
android:layout_height="45sp"
app:layout_constraintBottom_toTopOf="@+id/imageView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.77"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.08"
app:srcCompat="@drawable/icon_akar_cloud" />
<Button
style="@style/BaseButtonStyle"
android:id="@+id/button5"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="DARK MODE"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/imageView3"
app:layout_constraintVertical_bias="0.084"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="SETTINGS"
android:textSize="40sp"
android:textStyle="bold|italic"
android:textColor="@color/textColor"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.422"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.155" />
<ImageView
android:id="@+id/imageView14"
android:layout_width="75sp"
android:layout_height="100sp"
android:layout_marginTop="112dp"
app:layout_constraintBottom_toTopOf="@+id/imageView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.88"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@drawable/settings_2_svgrepo_com" />
<Button
style="@style/BaseButtonStyle"
android:id="@+id/button6"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="FORCE DATA FETCH"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button5"
app:layout_constraintVertical_bias="0.047"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<Button
style="@style/BaseButtonStyle"
android:id="@+id/button7"
android:layout_width="200sp"
android:layout_height="90sp"
android:fontFamily="sans-serif-black"
android:text="CLEAR CACHE"
android:textSize="25sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button6"
app:layout_constraintVertical_bias="0.065"
app:strokeColor="#000000"
app:strokeWidth="7sp" />
<ImageView
android:id="@+id/imageView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleX="1.1"
android:scaleY="1.1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.49"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.981"
app:srcCompat="@drawable/icon_material_sharp_water" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WaterLevelActivity">
<ImageView
android:id="@+id/imageView10"
android:layout_width="376dp"
android:layout_height="251dp"
android:layout_marginStart="21dp"
android:layout_marginTop="341dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="182dp"
android:background="#FFFFFF"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.726"
app:srcCompat="@drawable/waterlevelgraph" />
<TableLayout
android:id="@+id/tableLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@android:color/transparent"
android:padding="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TableRow>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/table_border_with_lines"
android:padding="8dp"
android:text="Percent Full"
android:textColor="@color/textColor" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/table_border_with_lines"
android:padding="8dp"
android:textColor="@color/textColor" />
</TableRow>
<TableRow>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/table_border_with_lines"
android:padding="8dp"
android:text="Mean Water Level (ft)"
android:textColor="@color/textColor" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/table_border_with_lines"
android:padding="8dp"
android:textColor="@color/textColor" />
</TableRow>
<TableRow>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/table_border_with_lines"
android:padding="8dp"
android:text="Surface Area (acres)"
android:textColor="@color/textColor" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/table_border_with_lines"
android:padding="8dp"
android:textColor="@color/textColor" />
</TableRow>
</TableLayout>
<TextView
android:id="@+id/your_text_view_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="92dp"
android:layout_marginTop="300dp"
android:layout_marginEnd="93dp"
android:layout_marginBottom="399dp"
android:text="Historical Water Data"
android:textColor="@color/textColor"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@android:color/white"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center">
<TextView
android:id="@+id/waterLevelTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WATER LEVEL"
android:textSize="20sp"
android:textColor="@color/textColor"
android:textStyle="bold"
android:gravity="center"/>
<TextView
android:id="@+id/waterLevelValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="36sp"
android:textColor="@color/textColor"
android:gravity="center"/>
<TextView
android:id="@+id/feetMsl"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Feet MSL"
android:textSize="16sp"
android:textColor="@color/textColor"
android:gravity="center"/>
<TextView
android:id="@+id/dateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/textColor"
android:gravity="center"
android:layout_marginTop="16dp"/>
<TextView
android:id="@+id/levelStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/textColor"
android:gravity="center"
android:layout_marginTop="16dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".WeatherActivity">
<ImageView
android:id="@+id/day_svg"
android:layout_width="409dp"
android:layout_height="212dp"
android:src="@drawable/day"
app:layout_constraintBottom_toTopOf="@id/weather_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.723" />
<TextView
android:id="@+id/weather_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:textColor="@color/textColor"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@id/temperature"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="44dp"
android:textColor="@color/textColor"
android:textSize="24sp"
app:layout_constraintBottom_toTopOf="@id/wind_svg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/wind_svg"
android:layout_width="332dp"
android:layout_height="110dp"
android:src="@drawable/wind_svg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.504"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.615" />
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="16dp"
android:background="@android:color/transparent"
android:text="WIND"
android:textColor="#000000"
android:textSize="10dp"
app:layout_constraintStart_toStartOf="@id/wind_svg"
app:layout_constraintTop_toTopOf="@id/wind_svg" />
<TextView
android:id="@+id/wind_direction"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="216dp"
android:background="@android:color/transparent"
android:gravity="center"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="@id/wind_svg"
app:layout_constraintLeft_toLeftOf="@id/wind_svg"
app:layout_constraintRight_toRightOf="@id/wind_svg"
app:layout_constraintStart_toStartOf="@id/wind_svg"
app:layout_constraintTop_toTopOf="@id/wind_svg"
app:layout_constraintVertical_bias="0.494" />
<TextView
android:id="@+id/wind_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="44dp"
android:layout_marginTop="36dp"
android:textColor="#000000"
android:background="@android:color/transparent"
app:layout_constraintStart_toStartOf="@id/wind_svg"
app:layout_constraintTop_toTopOf="@id/wind_svg" />
<TextView
android:id="@+id/wind_gust"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="44dp"
android:layout_marginBottom="36dp"
android:background="@android:color/transparent"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="@id/wind_svg"
app:layout_constraintStart_toStartOf="@id/wind_svg" />
<ImageView
android:id="@+id/precipitation_svg"
android:layout_width="334dp"
android:layout_height="119dp"
android:src="@drawable/precipitation_svg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.493"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/wind_svg"
app:layout_constraintVertical_bias="0.104" />
<ImageView
android:layout_width="55dp"
android:layout_height="49dp"
android:layout_marginStart="240dp"
android:layout_marginTop="36dp"
android:background="@android:color/transparent"
android:src="@drawable/reshot_icon_weather_yrq52ezutw"
app:layout_constraintStart_toStartOf="@id/precipitation_svg"
app:layout_constraintTop_toTopOf="@id/precipitation_svg" />
<TextView
android:id="@+id/chance_precipitation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginTop="40dp"
android:background="@android:color/transparent"
android:textColor="#000000"
app:layout_constraintStart_toStartOf="@id/precipitation_svg"
app:layout_constraintTop_toTopOf="@id/precipitation_svg" />
<TextView
android:id="@+id/amount_precipitation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="36dp"
android:layout_marginBottom="40dp"
android:background="@android:color/transparent"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="@id/precipitation_svg"
app:layout_constraintStart_toStartOf="@id/precipitation_svg" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="20dp"
android:background="@android:color/transparent"
android:text="PRECIPITATION"
android:textSize="10dp"
android:textColor="#000000"
app:layout_constraintStart_toStartOf="@id/precipitation_svg"
app:layout_constraintTop_toTopOf="@id/precipitation_svg" />
<Button
android:id="@+id/weather_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="weather.gov"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/precipitation_svg" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FFFFFF"
android:backgroundTint="#949494"
android:padding="16dp">
<androidx.cardview.widget.CardView
android:id="@+id/rampMenuCard"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:cardBackgroundColor="#E9E9E9"
app:cardCornerRadius="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:background="#00FFFFFF"
android:orientation="horizontal">
<ImageView
android:id="@+id/statusIcon"
android:layout_width="80dp"
android:layout_height="150dp"
android:layout_gravity="center"
android:layout_weight="1"
android:background="#00FFFFFF"
app:srcCompat="@drawable/circle_guess" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:background="#00FFFFFF"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FFFFFF"
android:orientation="horizontal">
<TextView
android:id="@+id/rampName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:text="Unknown Ramp"
android:textAlignment="center"
android:textColor="#7F7F7F"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:background="#7F7F7F" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FFFFFF"
android:orientation="horizontal">
<TextView
android:id="@+id/rampStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:background="#00FFFFFF"
android:text="Unknown Status"
android:textAlignment="center"
android:textColor="#7F7F7F"
android:textSize="24sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#BB86FC</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<color name="backgroundColor">#08446F</color>
<color name="textColor">#FFFFFFFF</color>
<color name="buttonBackgroundColor">#9DCFED</color>
<color name="rampInfoText">#515151</color>
<color name="rampInfoBackground">#DAF0FD</color>
<color name="buttonTextColor">#FFFFFFFF</color>
<color name="rampStatusOpen">#C9FFD9</color>
<color name="rampStatusClosed">#FFCBC9</color>
<color name="rampStatusUnknown">#E9E9E9</color>
</resources>

View File

@ -0,0 +1,13 @@
<resources>
<style name="Theme.LakeWatch" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Other theme attributes -->
<item name="android:background">@color/backgroundColor</item> <!-- Set background color here -->
<item name="buttonStyle">@style/BaseButtonStyle</item>
</style>
<style name="BaseButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:backgroundTint">@color/buttonBackgroundColor</item>
<item name="android:textColor">@color/buttonTextColor</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary" >#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<!-- Normal/Light mode -->
<color name="backgroundColor">#ECF7FF</color>
<color name="textColor">#000000</color>
<color name="buttonBackgroundColor">#FFFFFF</color>
<color name="buttonTextColor">#000000</color>
<color name="rampInfoBackground">#FFFFFF</color>
<color name="rampStatusText">#8D8D8D</color>
<color name="rampInfoText">#8D8D8D</color>
<color name="rampStatusOpen">#C9FFD9</color>
<color name="rampStatusClosed">#FFCBC9</color>
<color name="rampStatusUnknown">#E9E9E9</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">LakeWatch</string>
</resources>

View File

@ -0,0 +1,12 @@
<resources>
<style name="Theme.LakeWatch" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Other theme attributes -->
<item name="android:background">@color/backgroundColor</item> <!-- Set background color here -->
<item name="buttonStyle">@style/BaseButtonStyle</item>
</style>
<style name="BaseButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:backgroundTint">@color/buttonBackgroundColor</item>
<item name="android:textColor">@color/buttonTextColor</item>
</style>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package edu.utsa.cs3443.lakewatch;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
}

View File

@ -0,0 +1,145 @@
* Lake Watch Architecture
** Classes
*** MainMenuActivity
- Responsible for launching all other activities (the following):
- ~BoatRampMenuActivity~
- ~WeatherActivity~
- ~SettingsActivity~
- ~WaterLevelActivity~
- Primary entrypoint for the application
*** SettingsActivity
- Responsible for the following:
- Handling dark mode and light mode toggling
- Forcing a data fetch
- Forcefully clearing the cache
- Data fetching
- Our app will be talking to external data sources beyond local files on the system
- Due to this external data sourcing, the app may end up out of synchronization with those sources and thus we want to allow a given user to force an update if it ever becomes necessary. It's a escape hatch of sorts.
- Cache clearing
- To avoid overloading those external data sources we're going to maintain a cache on the local device that will be preferenced for data fetching under certain criteria (such as difference in dates, a generated hash for a given data set, and potentially more).
- As such, it would be useful if anything ever ends up in a bad state to forcefully clear out this cache to allow data to be reindexed.
*** Settings Data
- Handle the logic behind the scenes for the ~SettingsActivity~ class. It's responsible for actually driving the data behind the view, like the data fetching and caching.
- Likewise, it's also responsible for setting the global lightmode and darkmode for the entire application.
*** WeatherActivity
- Controls general weather information local to Canyon Lake. That information includes:
- High and Low temperature
- Wind speed and direction
- Precipitation chances along with the amount of said precipitation
- A short weather description
- This view will also include a link to [[https://weather.gov]] for Canyon Lake which may include further information.
*** WeatherData
- This is the Model for the ~WeatherActivity~ view.
- Responsible for fetching the local weather conditions at Canyon Lake.
*** MainRampMenuActivity
- Responsible for displaying a vertically scrollable view of all simple boat ramp details, those details include:
- The name of the ramp
- The status of the ramp, is it open, closed, or is its status unknown?
- Color coding based on the status
- Also responsible for launching each individual ramp subview with more details on a given ramp when the ramp is tapped/clicked on
- Contains a list of ~BoatRampData~ objects to pull said data from
*** BoatRampActivity
- Responsible for actually displaying the long information on a given boat ramp, that information includes:
- The boat ramp name, which may be different from the address
- The boat ramp status, open/closed/unknown
- The boat ramp opening times (the officially stated times when the ramp is open if its status shows it as open)
- The address, that has an included copy button to make it easy to paste into a GPS app
- The operator of the ramp, who is actually in charge of it
- The last time the information on the ramp was updated
- Useful for determining if the ramp status may be incorrect
- An image of the boat ramp
*** BoatRampData
- The Model class responsible for handling data interactions for both the ~MainRampMenuActivity~ and the ~BoatRampActivity~
- It pulls data from various data sources that are all kludged together or, unfortunately, sometimes /manually/ gathered for display in the view classes
- The ~BoatRampData~ is intended to be used as a collection, thus the ~loadBoatRampData~ returns an ~ArrayList~ of the data so the ~MainRampMenuActivity~ can easily use it
**** BoatRampStatus
- Contained within the ~BoatRampData~ class
- It's an Enumeration for determining the various states of the ramp
- Since a given ramp can have more than two states (open/closed/unknown), it had to be an Enumeration
*** WaterLevelActivity
- Responsible for displaying information on Canyon Lake's current water level and other data; furthermore, it will show historical water level and other data as well as showing a water level graph for the past three years
- Its information includes:
- How full the lake is as a percentage and in feet
- The surface area covered by the lake in acres of the total lake
- How many feet below (or above) the full pool the current water level is
- The last time the information was updated
*** WaterLevelData
- Contains the following information:
- ~date~: the date for the given water data record
- ~waterLevel~: the level of the water for the given record as feet above the vertical datum
- ~surfaceArea~: the acres covered by the lake surface
- ~reservoirStorage~: actual storage at measured lake elevation
- ~conservationStorage~: reservoir storage - dead pool capacity (note: conservation storage is capped at conservation capacity)
- ~percentFull~: 100 * conservation storage/conservation capacity
- ~conservationCapacity~: storage at conservation pool elevation - dead pool capacity
- ~deadPoolCapacity~: storage at dead pool elevation
- ~WaterLevelData~ is intended to be used as a collection of objects because each object represents a single date's information, as such the ~loadWaterLevelData~ method returns an ~ArrayList~ of ~WaterLevelData~ with the newest data being the first object in the list.
- Some of the data may not be used, but it's /much/ faster and easier to load the full CSV without cutting out some columns of data, thus that's what we do to populate it
** Data Files
*** ~weather-gov-forcast.json~
This file was pulled from [[https://api.weather.gov/gridpoints/EWX/136,74/forecast]].
It contains all the relevant weather data for Canyon Lake. And yes, before you ask, EWX, 136,74 is the correct station for Canyon Lake.
We intend to hit that API as needed for the data, but will include a preloaded set as well in case something goes wrong with that endpoint.
*** ~waterdata.csv~
Please see the file, the entire top of that CSV includes comments as to its exact functionality and numbers as provided to us from following URL: [[https://www.waterdatafortexas.org/reservoirs/individual/canyon.csv]].
To summarize the data is in the following format:
| date | water_level | surface_area | reservoir_storage | conservation_storage | percent_full | conservation_capacity | dead_pool_capacity |
|------------|-------------|--------------|-------------------|----------------------|--------------|-----------------------|--------------------|
| 2024-06-19 | 885.41 | 5689.16 | 215710 | 215639 | 56.9 | 378781 | 71 |
All data is using feet or acres as relevant.
*** ~Canyon-Lake-WaterData-Graph.png~
That PNG is an example of the actual graph we intend to show in the app, it will serve as a fallback in case data can't be pulled for any reason such that the user isn't left with nothing on screen.
It contains the water level per month for the last three years plotted out based on the data in the ~waterdata.csv~ file.
*** BOAT RAMP DATA FROM HELL
We are breaking the boat ramp data down into its own section because of how ad-hoc the data consistency and availability is.
Some operators, like Comal County, are angels and have the data in a simple JSON serving API that we can hit and get up to date statuses for.
Another operator who at least posts data, is the United States Army Core of Engineers. It's great that they do post data, but! They post their data in a manually updated HTML page — so we get to go through the /fun/, /_fun_/ experience of doing data scrapping against those pages to get up to date information.
The worst operators, like WORD (yes, that's an operator), told me (Price Hiller) to effectively pound sand after waiting on hold for the better part of two hours. So for the WORD ramps, and others like them the data status is in god's hands.
For the record, we only have up to date data we can pull on the ramps for approximately 14 ramps give or take a few. There's 23 total. A lot of the data will be manually entered for the purposes of this section based on news articles and hearsay.
Do take note that the ~Canyon-Lake-USACE-...~ directories are included for the relevant HTML files and are not going to be discussed below as during our scrape those directories won't exist, our scraper will see the full webpage.
**** ~Comal-County-Ramp-Info.json~
Comal County posts their ramp information off of their GIS system for the public to use. Under the ~features~ key within the JSON, they have per ramp statuses which we will be leveraging for their ramp information.
**** ~Canyon-Lake-USACE-Operators.html~
This file includes *all* of the operators of every Canyon Lake boat ramp. This makes it easy on us to assign names to ramps, and determine who actually owns the ramps.
**** ~Canyon-Lake-USACE.html~
This file has all the United States Army Corps of Engineer's data on their ramps. That data has the names of the ramps and whether they are closed or not and (sometimes) operating hours.

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More