initial commit

This commit is contained in:
2019-04-24 00:27:23 -07:00
commit 0b773d4326
60 changed files with 2344 additions and 0 deletions

156
lib/database_helper.dart Normal file
View File

@@ -0,0 +1,156 @@
import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class DatabaseHelper {
static final _databaseName = "LnLShare.db";
static final _databaseVersion = 1;
static final table = 'gmaplocations';
static final weatherTable = 'weather';
static final columnMapLocation = 'mapLocation';
static final columnLatLong = 'latLong';
static final columnLnlTime = 'lnlTime';
static final columnViewTime = 'viewTime';
static final columnElev = 'elev';
static final columnElevTime = 'elevTime';
static final columnWeatherID = 'weatherID';
static final columnWeatherWeatherID = 'weatherID';
static final columnWeather = 'weather';
static final columnWeatherForecast = 'weatherForecast';
// make this a singleton class
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
// only have a single app-wide reference to the database
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed
_database = await _initDatabase();
return _database;
}
// this opens the database (and creates it if it doesn't exist)
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}
// SQL code to create the database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnMapLocation VARCHAR UNIQUE,
$columnLatLong TEXT,
$columnLnlTime INT,
$columnViewTime INT,
$columnElev INT,
$columnElevTime INT,
$columnWeatherID INT
)
''');
await db.execute('''
CREATE TABLE $weatherTable (
$columnWeatherWeatherID INT UNIQUE,
$columnWeather TEXT,
$columnWeatherForecast TEXT
)
''');
}
Future<int> insert(Map<String, dynamic> row) async {
Database db = await instance.database;
return await db.insert(table, row);
}
Future<int> insertWeatherRow(Map<String, dynamic> row) async {
Database db = await instance.database;
return await db.insert(weatherTable, row);
}
Future<List<Map<String, dynamic>>> queryAllRows() async {
Database db = await instance.database;
return await db.query(table);
}
Future<int> queryRowCount() async {
Database db = await instance.database;
return Sqflite.firstIntValue(await db.rawQuery('SELECT COUNT(*) FROM $table'));
}
Future<int> queryRowExists(String mapLocation) async {
Database db = await instance.database;
return Sqflite.firstIntValue(await db.rawQuery('SELECT EXISTS (SELECT $columnMapLocation FROM $table WHERE $columnMapLocation=\'$mapLocation\')'));
}
Future<int> queryWeatherIDExists(int weatherID) async {
Database db = await instance.database;
return Sqflite.firstIntValue(await db.rawQuery('SELECT EXISTS (SELECT $columnWeatherWeatherID FROM $weatherTable WHERE $columnWeatherWeatherID=\'$weatherID\')'));
}
Future<String> queryNewestMapLocation() async {
Database db = await instance.database;
List<Map> result = await db.rawQuery('SELECT $columnMapLocation FROM $table ORDER BY $columnViewTime DESC LIMIT 1');
return (result.length == 0) ? 'Plataea\nGreece\nhttps://maps.app.goo.gl/1NW9z': result[0]['mapLocation'];
}
Future<List<String>> sortedMapLocations() async {
Database db = await instance.database;
var result = await db.rawQuery('SELECT $columnMapLocation FROM $table ORDER BY $columnViewTime DESC');
List<String> result_list = new List<String>();
for (var i = 0; i < result.length; i++) {
result_list.add(result[i]['mapLocation']);
}
return result_list;
}
Future<String> queryLatNLong(String mapLocation) async {
Database db = await instance.database;
List<Map> result = await db.rawQuery('SELECT $columnLatLong FROM $table WHERE $columnMapLocation = ?',[mapLocation]);
return (result[0]['latLong'] == null) ? 'NA' : result[0]['latLong'];
}
Future<String> queryWeather(int weatherID) async {
Database db = await instance.database;
List<Map> result = await db.rawQuery('SELECT $columnWeather FROM $weatherTable WHERE $columnWeatherWeatherID = ?',[weatherID]);
return result[0]['weather'];
}
Future<int> queryElevation(String mapLocation) async {
Database db = await instance.database;
List<Map> result = await db.rawQuery('SELECT $columnElev FROM $table WHERE $columnMapLocation = ?',[mapLocation]);
return result[0]['elev'];
}
Future<int> queryWeatherID(String mapLocation) async {
Database db = await instance.database;
List<Map> result = await db.rawQuery('SELECT $columnWeatherID FROM $table WHERE $columnMapLocation = ?',[mapLocation]);
return result[0]['weatherID'];
}
Future<int> update(Map<String, dynamic> row) async {
Database db = await instance.database;
String mapLocation = row[columnMapLocation];
return await db.update(table, row, where: '$columnMapLocation = ?', whereArgs: [mapLocation]);
}
Future<int> updateWeathTbl(Map<String, dynamic> row) async {
Database db = await instance.database;
int weatherID = row[columnWeatherWeatherID];
return await db.update(weatherTable, row, where: '$columnWeatherWeatherID = ?', whereArgs: [weatherID]);
}
Future<int> delete(String ml) async {
Database db = await instance.database;
return await db.delete(table, where: '$columnMapLocation = ?', whereArgs: [ml]);
}
}

View File

@@ -0,0 +1,144 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'database_helper.dart';
import 'dart:convert';
final EdgeInsets myBoxPadding = EdgeInsets.all(10.0);
final Color navy = Color(0xff00293C);
final Color peacock_blue = Color(0xff1e656d);
final Color ivory = Color(0xfff1f3ce);
final Color candy_apple = Color(0xfff62a00);
final String weatherApiUrlPrefix = 'https://api.openweathermap.org/data/2.5/weather?';
final String weatherForecastApiUrlPrefix = 'https://api.openweathermap.org/data/2.5/forecast?';
Future<String> getWeather(String mapLocation, String lat_Long) async {
// https://dragosholban.com/2018/07/01/how-to-build-a-simple-weather-app-in-flutter
final dbHelper = DatabaseHelper.instance;
String weatherApiID = await getPreferenceOpenWeatherMapApiKey();
if (weatherApiID == 'none?') { return null;}
else {
int weatherID = await dbHelper.queryWeatherID(mapLocation);
if (weatherID == null) {
List<String> latLong = lat_Long.split(',');
String weatherApiUrl = '${weatherApiUrlPrefix}lat=${latLong[0]}&lon=${latLong[1]}&units=imperial&appid=${weatherApiID}';
Response response = await get(weatherApiUrl);
if (response.statusCode == 200) {
Map<String, dynamic> responseJson = jsonDecode(response.body);
weatherID = responseJson['id'];
Map<String, dynamic> row = {
DatabaseHelper.columnMapLocation: mapLocation,
DatabaseHelper.columnWeatherID: weatherID
};
await dbHelper.update(row);
Map<String, dynamic> row2 = {
DatabaseHelper.columnWeatherWeatherID: weatherID,
DatabaseHelper.columnWeather: response.body
};
int weatherIDExists = await dbHelper.queryWeatherIDExists(weatherID);
if (weatherIDExists == 0) {
dbHelper.insertWeatherRow(row2);
} else {
dbHelper.updateWeathTbl(row2);
}
return response.body;
}
} else {
String weather = await dbHelper.queryWeather(weatherID);
return(weather);
}
}
return null;
}
BoxDecoration myBoxDecoration(Color color) {
return BoxDecoration(
color: color,
border: Border.all(
width: 2.0,
),
borderRadius: new BorderRadius.all(new Radius.circular(6.0)),
boxShadow: [
new BoxShadow(
offset: new Offset(2.0, 1.0),
blurRadius: 1.0,
spreadRadius: 1.0,
)
],
);
}
int newTimeStamp() {
DateTime date = new DateTime.now();
return (date.millisecondsSinceEpoch / 1000).floor();
}
Future<String> parseMapUrl(String mapUrl) async {
Response response = await get(mapUrl);
RegExp gmapUrl = new RegExp(r'https://www.google.com/(maps/preview)/(place)/([^/]*)/([^/]*)/data');
String mapInfo = response.body;
Match match = gmapUrl.firstMatch(mapInfo);
String subMapInfo = match.group(4);
RegExp subGmapUrl = new RegExp(r'@([^,]*,[^,]*),([^,]*,[^,]*)');
Match subMatch = subGmapUrl.firstMatch(subMapInfo);
return subMatch.group(1);
}
Future<String> getPreferenceElevationServer() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String elevationServer = prefs.getString("elevationServer") ?? "https://example.com";
return elevationServer;
}
Future<bool> setPreferenceElevationServer(String elevationServer) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("elevationServer",elevationServer);
return prefs.commit();
}
Future<String> getPreferenceOpenWeatherMapApiKey() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String apiKey = prefs.getString("openWeatherMapApiKey") ?? "none?";
return apiKey;
}
Future<bool> setPreferenceOpenWeatherMapApiKey(String apiKey) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("openWeatherMapApiKey",apiKey);
return prefs.commit();
}
Future<int> fetchElevation(String mapLocation) async {
final dbHelper = DatabaseHelper.instance;
String latLong = await dbHelper.queryLatNLong(mapLocation);
if (latLong == 'NA') { return null;}
else {
String elevationServer = await getPreferenceElevationServer();
String elevationApiUrl = elevationServer + '/api/v1/lookup\?locations\=' + latLong;
Response response = await get(elevationApiUrl);
if (response.statusCode == 200) {
Map<String, dynamic> responseJson = jsonDecode(response.body);
int elevation = responseJson['results'][0]['elevation'];
return elevation;
} else {return null;}
}
}
String convertCoordinates(String latNLong) {
List<String> latLong = latNLong.split(',');
List<String> lat = latLong[0].split('.');
List<String> long = latLong[1].split('.');
String latMinSec = getMinSec(lat[1]);
String longMinSec = getMinSec(long[1]);
String gpsDMS = lat[0] + '\u00B0 ' + latMinSec + ',' + long[0] + '\u00B0 ' + longMinSec;
return gpsDMS;
}
String getMinSec(String numString) {
String numString_x = '0.' + numString;
double num = double.parse(numString_x);
int mins = (num * 100 * 0.6).floor();
double secs = ((((num * 100 * 0.6) % 1) * 100000 * .6).round() / 1000);
String minSecs = mins.toString() + '\' ' + secs.toString();
return minSecs;
}

321
lib/main.dart Normal file
View File

@@ -0,0 +1,321 @@
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'global_helper_functions.dart';
import 'database_helper.dart';
import 'weather.dart';
import 'settings.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
void main() => runApp(MainApp());
class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Lat\'n Long Share',
theme: new ThemeData(
primaryColor: navy,
),
home: LatNLong(),
);
}
}
class LatNLong extends StatefulWidget {
@override
_LatNLongState createState() => new _LatNLongState();
}
class _LatNLongState extends State<LatNLong> {
// https://stackoverflow.com/a/32437518 <- to here for time zone api info
final dbHelper = DatabaseHelper.instance;
static const platform = const MethodChannel('app.channel.shared.data');
RegExp gmapExp = new RegExp(r'(https://maps.app.goo.gl/)(.*$)');
String widgetMapLocation = "none";
String latnLong = "none";
String latnLongDMS = "none";
int elevation;
int feetElevation;
Map<String, dynamic> _weather = {
"id":0,
"weather":[
{
"description":"none",
"icon":"none"
}
],
"main": {
"temp":0,
"pressure":0,
"humidity":0,
"temp_min":0,
"temp_max":0
},
"visibility":0,
"wind": {
"speed":0,
"deg":0
},
"dt":0,
"sunrise":0,
"sunset":0
};
@override
void initState() {
super.initState();
updateState();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: peacock_blue,
appBar: new AppBar(
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.settings), onPressed: _settings);
},
),
title: new Text('Lat N Long Share'),
actions: <Widget>[
new IconButton(icon: const Icon(Icons.list), onPressed: _pushSaved),
],
),
body: SingleChildScrollView(
child: new Container(
padding: EdgeInsets.all(20.0),
child: new Wrap(
spacing: 20.0,
runSpacing: 20.0,
alignment: WrapAlignment.spaceAround,
children: <Widget>[
Container(
padding: myBoxPadding,
decoration: myBoxDecoration(ivory),
child: Text(
'DD / DMS: \n(${this.latnLong})\n(${this.latnLongDMS})' ,
),
),
Container(
padding: myBoxPadding,
decoration: myBoxDecoration(ivory),
child: Text(
this.widgetMapLocation,
),
),
Container(
padding: myBoxPadding,
decoration: myBoxDecoration(ivory),
child: Text(
'elevation: ${this.feetElevation.toString()} feet, ${this.elevation.toString()} meters',
),
),
Weather(weather: this._weather,),
],
),
),
),
);
}
void _settings() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Settings()),
);
}
void _pushSaved() async {
List<String> sorted_Map_Locations = await dbHelper.sortedMapLocations();
await Navigator.of(context).push(
new MaterialPageRoute<void>(
builder: (BuildContext context) {
return new Scaffold(
backgroundColor: peacock_blue,
appBar: new AppBar(
title: const Text('Previous Locations'),
),
body: _buildHistory(sorted_Map_Locations),
);
},
),
);
}
Widget _buildHistory(List<String> sorted_Map_Locations) {
return new ListView.builder(
itemCount: sorted_Map_Locations.length,
itemBuilder: (BuildContext ctxt, int i) {
return new InkWell(
onTap: () {
selectMapLocation(sorted_Map_Locations[i]);
Navigator.of(context).pop();
},
child: new Card(
margin: EdgeInsets.all(4.0),
color: Colors.black,
child: new Card(
margin: EdgeInsets.all(4.0),
color: ivory,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Expanded(
child: Text(
sorted_Map_Locations[i],
),
),
Container(
child: new Icon(
Icons.arrow_right,
size: 50.0,
color: candy_apple,
),
),
],
),
),
),
),
);
},
);
}
Future<void> updateState() async {
var sharedData = await platform.invokeMethod("getSharedText");
if (sharedData != null && sharedData is String) {
if (gmapExp.hasMatch(sharedData)) {
setState(() {
this.widgetMapLocation = sharedData;
});
}
} else if (sharedData == null) {
sharedData = await dbHelper.queryNewestMapLocation();
if (sharedData != null && sharedData is String) {
setState(() {
this.widgetMapLocation = sharedData;
});
}
}
await _Create_Row_If_Needed(sharedData);
await _populateLatNLong(sharedData);
}
Future<void> _populateWeather(String mapLocation, String latLong) async {
String weather = await getWeather(mapLocation,latLong);
if (weather != null) {
Map<String, dynamic> weatherJson = jsonDecode(weather);
if (weatherJson != this._weather) {
setState(() {
this._weather = weatherJson;
});
}
}
}
Future<void> _populateElevation(String mapLocation) async {
await dbHelper.queryElevation(mapLocation).then((int elev) {
if (elev != this.elevation) {
if (elev != null) {
setState(() {
this.elevation = elev;
this.feetElevation = (elev * 3.28084).round();
});
}
}
if (elev == null) {
final int timeStamp = newTimeStamp();
updateElevation(timeStamp, mapLocation);
}
});
}
Future<void> updateElevation(int timeStamp, String mapLocation) async {
await fetchElevation(mapLocation).then((int elev) {
if (elev != null) {
setState(() {
this.elevation = elev;
this.feetElevation = (elev * 3.28084).round();
});
} else {
setState(() {
this.elevation = null;
this.feetElevation = null;
});
}
Map<String, dynamic> row = {
DatabaseHelper.columnMapLocation: mapLocation,
DatabaseHelper.columnElevTime: timeStamp,
DatabaseHelper.columnElev: elev
};
dbHelper.update(row);
});
}
Future<void> _populateLatNLong(String mapLocation) async {
await dbHelper.queryLatNLong(mapLocation).then((String lat_long) {
if ((lat_long != this.latnLong) && (lat_long != 'NA')) {
_populateElevation(mapLocation);
_populateWeather(mapLocation, lat_long);
setState(() {
this.latnLong = lat_long;
this.latnLongDMS = convertCoordinates(lat_long);
});
}
if (lat_long == 'NA') {
final int timeStamp = newTimeStamp();
updateLatNLong(timeStamp, mapLocation);
}
});
}
Future<void> updateLatNLong(int timeStamp, String mapLocation) async {
String mapUrl = gmapExp.stringMatch(mapLocation);
await parseMapUrl(mapUrl).then((String lat_long) {
setState(() {
this.latnLong = lat_long;
this.latnLongDMS = convertCoordinates(lat_long);
});
Map<String, dynamic> row = {
DatabaseHelper.columnMapLocation: mapLocation,
DatabaseHelper.columnLnlTime: timeStamp,
DatabaseHelper.columnLatLong: lat_long
};
dbHelper.update(row);
updateElevation(timeStamp, mapLocation);
_populateWeather(mapLocation, lat_long);
});
}
Future<void> selectMapLocation(String mapLocation) async {
int time_Stamp = newTimeStamp();
Map<String, dynamic> row = {
DatabaseHelper.columnMapLocation: mapLocation,
DatabaseHelper.columnViewTime: time_Stamp
};
setState(() {
this.widgetMapLocation = mapLocation;
});
await dbHelper.update(row);
await _populateLatNLong(mapLocation);
}
Future<void> _Create_Row_If_Needed(String mapLocation) async {
final int rowExists = await dbHelper.queryRowExists(mapLocation);
final int timeStamp = newTimeStamp();
if (rowExists == 0) {
Map<String, dynamic> row = {
DatabaseHelper.columnMapLocation: mapLocation,
DatabaseHelper.columnViewTime: timeStamp
};
final id = await dbHelper.insert(row);
}
}
}

153
lib/settings.dart Normal file
View File

@@ -0,0 +1,153 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/material.dart';
import 'global_helper_functions.dart';
class Settings extends StatefulWidget {
@override
_SettingsState createState() => new _SettingsState();
}
class _SettingsState extends State<Settings> {
String elevationServer = "none";
String openWeatherMapKey = "none";
String shortOWMAK = "none";
final _elevationServerController = TextEditingController();
final _oWMKController = TextEditingController();
@override
void initState() {
super.initState();
updateState();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: peacock_blue,
appBar: new AppBar(
title: const Text('Settings'),
),
body: new ListView(
children: <Widget> [
new Container(
padding: myBoxPadding,
decoration: myBoxDecoration(ivory),
child: Column(
children: [
Text(
'Open-Elevation Api Server to Use',
),
Text(
'Current Value: ' + this.elevationServer,
),
TextField(
controller: _elevationServerController,
decoration: InputDecoration(
hintText: this.elevationServer,
),
),
Container(
margin: EdgeInsets.only(
top: 20.0,
bottom: 5.0,
),
child: RaisedButton.icon(
label: Text(
"Update Elevation Server",
),
icon: Icon(
Icons.refresh,
size: 50.0,
),
color: candy_apple,
onPressed: () { _updateElevationServer(); },
),
),
],
),
),
new Container(
padding: myBoxPadding,
decoration: myBoxDecoration(ivory),
child: Column(
children: [
Text(
'Open Weather Map Api Key to Use',
),
Text(
'Current Value: ${this.shortOWMAK}...',
),
TextField(
controller: _oWMKController,
decoration: InputDecoration(
hintText: '${this.shortOWMAK}...',
),
),
Container(
margin: EdgeInsets.only(
top: 20.0,
bottom: 5.0,
),
child: RaisedButton.icon(
label: Text(
"Update ... Api Key",
),
icon: Icon(
Icons.refresh,
size: 50.0,
),
color: candy_apple,
onPressed: () { _updateOpenWeatherMapApiKey(); },
),
),
],
),
),
],
),
);
}
Future<void> updateState() async {
String evServer = await getPreferenceElevationServer();
String oWMK = await getPreferenceOpenWeatherMapApiKey();
if ((evServer != this.elevationServer) || (oWMK != this.openWeatherMapKey)) {
setState((){
this.elevationServer = evServer;
this.openWeatherMapKey = oWMK;
this.shortOWMAK = oWMK.substring(0,5);
});
}
}
void _updateOpenWeatherMapApiKey() {
String oWMK = _oWMKController.text;
if (oWMK.length > 0) {
setPreferenceOpenWeatherMapApiKey(oWMK).then((bool committed) {
if (oWMK != this.openWeatherMapKey) {
setState((){
this.openWeatherMapKey = oWMK;
this.shortOWMAK = oWMK.substring(0,5);
});
}
});
Navigator.pop(context);
}
}
void _updateElevationServer() {
String elevServer = _elevationServerController.text;
RegExp urlValidator = new RegExp(r'^http[s]?://.+$');
if (urlValidator.hasMatch(elevServer)) {
setPreferenceElevationServer(elevServer).then((bool committed) {
if (elevServer != this.elevationServer) {
setState((){
this.elevationServer = elevServer;
});
}
});
Navigator.pop(context);
}
}
}

34
lib/weather.dart Normal file
View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'global_helper_functions.dart';
class Weather extends StatefulWidget {
Map<String, dynamic> weather;
Weather({Key key, this.weather}) : super(key: key);
@override
_WeatherState createState() => new _WeatherState();
}
class _WeatherState extends State<Weather> {
@override
Widget build(BuildContext context) {
return Container(
padding: myBoxPadding,
decoration: myBoxDecoration(ivory),
child: Wrap(
spacing: 10.0,
children: <Widget>[
Text(
'Current Weather Conditions: ${widget.weather['weather'][0]['description']}',
),
Text(
'Weather location: ${widget.weather['id']}',
),
],
)
);
}
}