Compare commits
20 Commits
ee116bed8d
...
dev
Author | SHA1 | Date | |
---|---|---|---|
ad4630e495 | |||
2ee8eaecc8 | |||
13c532e87f | |||
70eaa8a4fb | |||
3ae27fdda4 | |||
4132aa0f83 | |||
6e2d293cdc | |||
7ffcfee1d3 | |||
7e53a2b4e2 | |||
b1d01c6915 | |||
c2995dac6d | |||
4483c1ebb0 | |||
8c8b848090 | |||
54ae255e9e | |||
7a48d137e0 | |||
0f475773de | |||
d8cc22ce80 | |||
c2537f78cd | |||
b598f4cddb | |||
8cc609ff4e |
7
.gitignore
vendored
7
.gitignore
vendored
@ -242,3 +242,10 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
|
||||||
|
# Do not post secrets!
|
||||||
|
lib/var/secrets.dart
|
||||||
|
|
||||||
|
# Keep locals local.
|
||||||
|
lib/var/local.dart
|
||||||
|
lib/var/config.dart
|
||||||
|
3
assets/README.md
Normal file
3
assets/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Assets
|
||||||
|
|
||||||
|
Images and other media for Buddy.
|
4
bin/README.md
Normal file
4
bin/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# bin
|
||||||
|
|
||||||
|
Helper scripts forthose new to Flutter or for myself if I take a break and need
|
||||||
|
reminders. ;D
|
3
bin/build.sh
Normal file
3
bin/build.sh
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# TODO / TBD
|
3
bin/run.sh
Executable file
3
bin/run.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
flutter run -d chrome
|
3
bin/test.sh
Executable file
3
bin/test.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
flutter run -d linux
|
51
lib/helpers/http.dart
Normal file
51
lib/helpers/http.dart
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// File for helper functions.
|
||||||
|
|
||||||
|
import '/var/config.dart';
|
||||||
|
import '/var/secrets.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
//import 'dart:convert';
|
||||||
|
|
||||||
|
// Generic method to hit a GET request and return the response.
|
||||||
|
Future<String> hitAPI(String url) async {
|
||||||
|
if (debug) debugPrint("DEBUG: URL is '$url'.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
http.Response response = await http.get(Uri.parse(url));
|
||||||
|
|
||||||
|
String data = "";
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
data = response.body;
|
||||||
|
//var decodedData = jsonDecode(data);
|
||||||
|
//data = decodedData.toString();
|
||||||
|
//data = jsonEncode(decodedData);
|
||||||
|
if (debugVerbose) {
|
||||||
|
debugPrint("DEBUG-VERBOSE: Response data: \n\n$data\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (debug) {
|
||||||
|
debugPrint(
|
||||||
|
"DEBUG: Response failed with code '${response.statusCode.toString()}'",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
return "Sorry! It seems we have an issue: '${e.toString()}'.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// How to guide: https://openweathermap.org/current#zip
|
||||||
|
Future<String> hitOpenWeatherZip(String zipCode, String? countryCode) async {
|
||||||
|
countryCode ??= "US";
|
||||||
|
|
||||||
|
String url =
|
||||||
|
'$openWeatherAPI'
|
||||||
|
'?zip=$zipCode,$countryCode'
|
||||||
|
'&units=imperial'
|
||||||
|
'&appid=$openWeatherKey';
|
||||||
|
|
||||||
|
String data = await hitAPI(url);
|
||||||
|
return data;
|
||||||
|
}
|
177
lib/helpers/json.dart
Normal file
177
lib/helpers/json.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
// Functions related to parsing JSON strings and objects.
|
||||||
|
|
||||||
|
import '/var/config.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
String formatOpenWeatherData(var data) {
|
||||||
|
String location = pullOpenWeatherCity(data),
|
||||||
|
temp = pullOpenWeatherTemp(data),
|
||||||
|
conditions = pullOpenWeatherConditions(data),
|
||||||
|
windSpeed = pullOpenWeatherWind(data),
|
||||||
|
humidity = pullOpenWeatherHumidity(data);
|
||||||
|
|
||||||
|
String windChill = getWindChill(temp, windSpeed),
|
||||||
|
heatIndex = getHeatIndex(temp, humidity),
|
||||||
|
windAnnoyance = getWindAnnoyance(temp, windSpeed),
|
||||||
|
feelsLike = getUniversalThermalClimateIndex();
|
||||||
|
|
||||||
|
String comfort = "";
|
||||||
|
|
||||||
|
String text =
|
||||||
|
"$location is $temp$tempUnits and $conditions"
|
||||||
|
" with a wind speed of $windSpeed$windUnits"
|
||||||
|
" and humidity of $humidity$humidityUnits."
|
||||||
|
" $windChill"
|
||||||
|
" $heatIndex"
|
||||||
|
" $windAnnoyance"
|
||||||
|
" $feelsLike"
|
||||||
|
" $comfort";
|
||||||
|
|
||||||
|
final String doubleSpace = " ", singleSpace = " ";
|
||||||
|
while (text.contains(doubleSpace)) {
|
||||||
|
text = text.replaceAll(doubleSpace, singleSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pullOpenWeatherCity(var data) {
|
||||||
|
String location = "${data['city']['name']} (${data['city']['country']})";
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: location = '$location'");
|
||||||
|
}
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pullOpenWeatherTemp(var data) {
|
||||||
|
String temp = data['list'][0]['main']['temp'].toString();
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: temp = '$temp'");
|
||||||
|
}
|
||||||
|
return temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pullOpenWeatherConditions(var data) {
|
||||||
|
String conditions = data['list'][0]['weather'][0]['description'].toString();
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: conditions = '$conditions'");
|
||||||
|
}
|
||||||
|
return conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pullOpenWeatherWind(var data) {
|
||||||
|
String wind = data['list'][0]['wind']['speed'].toString();
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: wind = '$wind'");
|
||||||
|
}
|
||||||
|
return wind;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pullOpenWeatherHumidity(var data) {
|
||||||
|
String humidity = data['list'][0]['main']['humidity'].toString();
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: humidity = '$humidity'");
|
||||||
|
}
|
||||||
|
return humidity;
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcWindChill(double temp, double windSpeed) {
|
||||||
|
// ## Wind Chill ##
|
||||||
|
// # Wind speed as noted in: https://answers.yahoo.com/question/index?qid=20091020183148AAHm3kB&guccounter=1
|
||||||
|
// # More official source: https://www.weather.gov/media/epz/wxcalc/windChill.pdf
|
||||||
|
double windChill =
|
||||||
|
35.74 +
|
||||||
|
(0.6215 * temp) -
|
||||||
|
(35.75 * pow(windSpeed, 0.16)) +
|
||||||
|
(0.4275 * temp * pow(windSpeed, 0.16));
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: windChill = '$windChill'");
|
||||||
|
}
|
||||||
|
return windChill;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getWindChill(String temp, String windSpeed) {
|
||||||
|
double temperature = double.parse(temp);
|
||||||
|
double wind = double.parse(windSpeed);
|
||||||
|
|
||||||
|
if ((temperature < 50 && wind > 5) || debug) {
|
||||||
|
double windChill = calcWindChill((temperature), (wind));
|
||||||
|
return "My guess is that's a wind chill of $windChill$tempUnits.";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcHeatIndex(double temp, double humidity) {
|
||||||
|
// ## Heat Index ##
|
||||||
|
// # Official formula: https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
|
||||||
|
double heatIndex =
|
||||||
|
-42.379 +
|
||||||
|
2.04901523 * temp +
|
||||||
|
10.14333127 * humidity -
|
||||||
|
0.22475541 * temp * humidity -
|
||||||
|
0.00683783 * temp * temp -
|
||||||
|
0.05481717 * humidity * humidity +
|
||||||
|
0.00122874 * temp * temp * humidity +
|
||||||
|
0.00085282 * temp * humidity * humidity -
|
||||||
|
0.00000199 * temp * temp * humidity * humidity;
|
||||||
|
|
||||||
|
if (humidity < 13 && temp >= 80 && temp <= 112) {
|
||||||
|
heatIndex -= ((13 - humidity) / 4) * sqrt((17 - (temp - 95.0).abs()) / 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (humidity > 85 && temp >= 80 && temp <= 87) {
|
||||||
|
heatIndex += ((humidity - 85) / 10) * ((87 - temp) / 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heatIndex < 80) {
|
||||||
|
heatIndex =
|
||||||
|
0.5 * (temp + 61.0 + ((temp - 68.0) * 1.2) + (humidity * 0.094));
|
||||||
|
heatIndex = (heatIndex + temp) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: heatIndex = '$heatIndex'");
|
||||||
|
}
|
||||||
|
return heatIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getHeatIndex(String temp, String humidity) {
|
||||||
|
double temperature = double.parse(temp);
|
||||||
|
double humid = double.parse(humidity);
|
||||||
|
|
||||||
|
if (temperature > 80 || debug) {
|
||||||
|
double heatIndex = calcHeatIndex((temperature), (humid));
|
||||||
|
return "My guess is that's a heat index of $heatIndex$tempUnits.";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcWindAnnoyance(double temp, double windSpeed) {
|
||||||
|
double windAnnoyance = (temp / (windSpeed * (windSpeed * 0.05)));
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: windAnnoyance = '$windAnnoyance'");
|
||||||
|
}
|
||||||
|
return windAnnoyance;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getWindAnnoyance(String temp, String windSpeed) {
|
||||||
|
double temperature = double.parse(temp);
|
||||||
|
double wind = double.parse(windSpeed);
|
||||||
|
|
||||||
|
double windAnnoyance = calcWindAnnoyance(temperature, wind);
|
||||||
|
|
||||||
|
if (windAnnoyance < 3) {
|
||||||
|
return "Wind may be a bit much for the temperature.";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcUniversalThermalClimateIndex() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUniversalThermalClimateIndex() {
|
||||||
|
return "";
|
||||||
|
}
|
219
lib/main.dart
219
lib/main.dart
@ -1,20 +1,233 @@
|
|||||||
|
// Local
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
//import '/var/secrets.dart';
|
||||||
|
import '/var/config.dart';
|
||||||
|
import 'helpers/http.dart';
|
||||||
|
import 'helpers/json.dart';
|
||||||
|
|
||||||
|
// Flutter / Dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MainApp());
|
runApp(const MainApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainApp extends StatelessWidget {
|
final String loadText = "Loading...";
|
||||||
|
|
||||||
|
class MainApp extends StatefulWidget {
|
||||||
const MainApp({super.key});
|
const MainApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MainApp> createState() => _MainAppState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainAppState extends State<MainApp> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
String _zipcode = "";
|
||||||
|
String _city = "";
|
||||||
|
String _country = "";
|
||||||
|
String _latlong = "";
|
||||||
|
|
||||||
|
String weather = loadText;
|
||||||
|
bool keepLoading = false;
|
||||||
|
DateTime lastLoadTime = DateTime.now().subtract(
|
||||||
|
Duration(seconds: limitRefreshSeconds),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_refreshWeather();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(
|
Widget form = Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 100,
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: _zipcode,
|
||||||
|
validator: (value) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_zipcode = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 100,
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: _city,
|
||||||
|
validator: (value) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_city = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 100,
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: _country,
|
||||||
|
validator: (value) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_country = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
width: 300,
|
||||||
|
child: TextFormField(
|
||||||
|
initialValue: _latlong,
|
||||||
|
validator: (value) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onSaved: (value) {
|
||||||
|
_latlong = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text("Save"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget weatherLayout = Column(
|
||||||
|
children: [Text(weather), CircularProgressIndicator()],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (weather == loadText) {
|
||||||
|
Future.delayed(Duration(seconds: 1), () {
|
||||||
|
setState(() {
|
||||||
|
keepLoading = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
weatherLayout = Column(
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
label: Text("Reload Weather"),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_refreshWeather();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(weather),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Text('Hello World!'),
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text('Weather Today!'),
|
||||||
|
form,
|
||||||
|
Expanded(child: weatherLayout),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert the Future value into a usable value.
|
||||||
|
pullWeather({
|
||||||
|
String? country,
|
||||||
|
String? zip,
|
||||||
|
String? city,
|
||||||
|
String? lat,
|
||||||
|
String? long,
|
||||||
|
}) async {
|
||||||
|
country ??= "US";
|
||||||
|
|
||||||
|
if (lat != null && long != null) {
|
||||||
|
weather = "Lat / Long Not Yet Implemented";
|
||||||
|
} else if (zip != null) {
|
||||||
|
weather = await hitOpenWeatherZip(zip, country);
|
||||||
|
} else if (city != null) {
|
||||||
|
weather = "City Weather Not Yet Implemented";
|
||||||
|
} else {
|
||||||
|
weather = "Please enter a location.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weather.toString().contains("Sorry!")) {
|
||||||
|
return weather;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weather != loadText) {
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: Formatting text.");
|
||||||
|
}
|
||||||
|
var weatherObject = jsonDecode(weather);
|
||||||
|
|
||||||
|
String weatherString = formatOpenWeatherData(weatherObject);
|
||||||
|
|
||||||
|
weather = weatherString;
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: Set to formatted weather string.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: Skipping text formatting.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the API and put the desired information into the screen variable.
|
||||||
|
_refreshWeather() {
|
||||||
|
var lastReloadSeconds = DateTime.now().difference(lastLoadTime).inSeconds;
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: Refresh was '$lastReloadSeconds' seconds ago.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastReloadSeconds < limitRefreshSeconds) {
|
||||||
|
debugPrint("DEBUG: Skipping reload.");
|
||||||
|
// TODO / TBD: Show a toast / scaffold snackbar that it cannot reload yet,
|
||||||
|
// or change the button text to "Please wait X seconds!".
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: Pulling weather...");
|
||||||
|
}
|
||||||
|
weather = loadText;
|
||||||
|
pullWeather(country: "US", zip: "47630");
|
||||||
|
lastLoadTime = DateTime.now();
|
||||||
|
if (debug) {
|
||||||
|
debugPrint("DEBUG: Weather pulled and date is set.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
15
lib/var/config.EXAMPLE.dart
Normal file
15
lib/var/config.EXAMPLE.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
// This file needs renamed `local.dart` when implemented.
|
||||||
|
|
||||||
|
// Basic
|
||||||
|
// Project-wide configuration variables for both testing and production.
|
||||||
|
const bool debug = true;
|
||||||
|
const bool debugVerbose = false;
|
||||||
|
const int limitRefreshSeconds = 60;
|
||||||
|
|
||||||
|
// OpenWeather Constants
|
||||||
|
// Settings for how to use the OpenWeather API.
|
||||||
|
const String openWeatherProtocol = "https://";
|
||||||
|
const String openWeatherHost = "api.openweathermap.org";
|
||||||
|
const String openWeatherURI = "/data/2.5/forecast";
|
||||||
|
const String openWeatherAPI =
|
||||||
|
"$openWeatherProtocol$openWeatherHost$openWeatherURI";
|
3
lib/var/secrets.EXAMPLE.dart
Normal file
3
lib/var/secrets.EXAMPLE.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// This file needs renamed `secrets.dart` and filled out properly if implemented.
|
||||||
|
|
||||||
|
final String openWeatherKey = "abc123";
|
3
notes/README.md
Normal file
3
notes/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Notes
|
||||||
|
|
||||||
|
Any calculations, tests, or general ideas for functionality within Buddy. :)
|
BIN
notes/WindyTempComfortCalculations.ods
Normal file
BIN
notes/WindyTempComfortCalculations.ods
Normal file
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
name: com_hyperling_buddy_website
|
name: com_hyperling_buddy_website
|
||||||
description: "A new Flutter project."
|
description: "Buddy, a weather-focused API frontend."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
http: ^1.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user