From 90fd4f39c8826a0644b1a11dca271f436ef2ecf8 Mon Sep 17 00:00:00 2001 From: Aritra Date: Sat, 6 Jul 2024 19:09:31 +0530 Subject: [PATCH] feat: Road KMl and Airplane KML implemented --- android/app/build.gradle | 24 +- android/app/src/main/AndroidManifest.xml | 3 +- .../MainActivity.kt | 6 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 544 -> 4184 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 442 -> 2843 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 721 -> 5802 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 1031 -> 8938 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 1443 -> 12858 bytes lib/components/location_selector.dart | 6 +- lib/components/navisland.dart | 10 +- lib/data_class/coordinate.dart | 10 +- lib/data_class/geo_reversecode_response.dart | 268 +++++++++++++++ lib/data_class/geocode_response.dart | 307 ++++++++++++++++++ lib/main.dart | 9 +- lib/screens/dashboard.dart | 2 +- lib/screens/kml_builder.dart | 65 +++- lib/screens/splashscreen.dart | 4 - lib/screens/tour_builder.dart | 16 +- lib/utils/api_manager.dart | 89 ++++- lib/utils/kmlgenerator.dart | 267 +++++++++++---- lib/utils/tour_controller.dart | 42 ++- pubspec.lock | 72 ++-- pubspec.yaml | 3 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 25 files changed, 1036 insertions(+), 171 deletions(-) delete mode 100644 android/app/src/main/kotlin/com/example/super_liquid_galaxy_controller/MainActivity.kt create mode 100644 lib/data_class/geo_reversecode_response.dart create mode 100644 lib/data_class/geocode_response.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index af703c5..3c44ea1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -12,6 +12,13 @@ if (localPropertiesFile.exists()) { } } +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + + def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -23,7 +30,7 @@ if (flutterVersionName == null) { } android { - namespace "com.example.super_liquid_galaxy_controller" + namespace "com.aritra.super_liquid_galaxy_controller" compileSdkVersion 34 ndkVersion flutter.ndkVersion @@ -42,7 +49,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.super_liquid_galaxy_controller" + applicationId "com.aritra.super_liquid_galaxy_controller" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion @@ -51,13 +58,20 @@ android { versionName flutterVersionName } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release } } + } flutter { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8e3be6a..b6db632 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + @@ -30,7 +31,7 @@ /> Px_5=lfsRCr$PTzPa<*LnZ$+h#N~(u`)2K?n^4f*^qqGKL^Pn@zwCsdMb&Qv%d~ zVA|M@JAWUEL|y}ctH%A&-<_78lWBR%rw=g)nB(<&_XYxiuX{Wmp67WW z^roghsYKsq>9t99Rs6)X0*-EJz1qJ{$< zDmg#$A*vtj)xP8Uei0gw(3%d*?@^73Bvcs#NwiX6i*V|uox zX<9-;0@T&j(WgjG->+Sy@@t3l=O$h(sd5Fbuvw=1bEwltQ+= zy!>h~7|aELu+a#r6%`eW0pKzKpvt1rpY&@&vl*i|IzTp? zZGS;Q!7hejFgP5ydYR8cfH;m*dwP1Du!+K_V%KRZ3KWfzR)TL01p%t z6uf~(T2<9aGt$9eP_3-2oHiqEu7f7i6GXfD+WGV6Cr*-^*91Wz6%`fdgTdgP=DKZ? z-Iu9Z6#s}`9h?7ST!COsG``T=b~qfrDl9CV6hqB0j2S3CCd=}nNm4IUK+JH#6qLdB zkP84DMmiySGH`%+jTfw;OATuz5+UPVYG9CPu~=}aaU~cGE(HK-0uqFvVE^!SXe90h zfF5k0@K)RQjMWeAwO;s!zXR^9{@l(0CY#8HQr!saIv~_<;oYmUN5?6 zb^?o4t*WXj9{|pbbFpdyVrvhkKx>wGKLNoNH>UD?8osartYis$wvvrzfJafu9LYVk{cXbjRN1%a{Mq zk^G0jF20OTdiaj#EWHfwEC?yPLv0w3^hn?4jq;Nar} zAGDIC_wA(qJHJU~u@?c-2*Xj9w=fLLGgMQcCsYepP8|NQ@!|)M-1120hlKS*XTucP z;lc;}OyYU|@kNUk{dD&1*%kyxQEtD^#AOBEQ&UsZa`oy}OqjiE*d0>>h9=u^dOy-YiZeY7MkN&Tt#)&ZKB-KA<7-?vfSP+Nv++A*485_t)i;7D+BrDkA?vFL~(KP&+T@*qN?hk zx)}>n+T7es%F4>t0sz`XUGk%V!M~FMAO!$YU3NCz>ty{iJ)Ga2$hfWsrJ76i(!T+~ zH2}a#kOiOpN0~1?u#-!&is~nlKv+UxnB+nfK;1C`GDHdp!^(;j=1;t^b#J7v{aJi} zX;R|Lzc0*v$Y&K$;`CcpNZhS(d#& zKR^FHAyAF4t)K>z4EA_9I)y#T$MY7!94}>2%X*YI&uy>lO3HD@i_P6`5 zUN8U8EELy0nSmSovNPPx{Sn|9eJb?8iU6S)K;Q%!uMCO@$Fk(P>bl-|1}j>^`LD*1pe47GZWkWA zFZuEd&)hLT+@Vl`CuRUU;vf~3Y8DsAJb5rw`_tdtoIi~Ky`xmVxff@od7J<-50_s$ z|I+oQoKXUV3)1RcTin<8Kc3>3x)hCL4R?DRmuUj@)WOiTpZw+~PUv(=(8K}SR(|oq z;p@#y#sbjOo88y{@re|_cry-=9)e+jXm~MQf}YvzzW!VspaH`f2WT8B7MGwuj-=x% zHWnitm!P-=0T_WIGafe|m!P-=#U&`_CS+X2Vx$w}0L1~qbjRnjSdDwJxEG6ivA7o# zY;hZOn?$xs;{e?z)l0QFK!aP)!--KGps2n1sRYO)@XYq|ix*xRW77T$83*lN7zpmq zo|iJ(eKA?1s?Wlc&7~imKUyEk6R0lzvs(K_i6KZCz`c1wS>zAsZxDtf41@pUUV2+Zf^8L-&b-sq zcCW!+`&Lm=QLLR+tm-+(ad7V&KTQ4a`!+ZMdQ=tnvNer@rZaW)oe+Y29b?QyQK)A3 z@$iF#p&JK|={u|7zGwc~xx4OJtlSJ!!Z9Q6&W;NpO_dd^!^3`fRrjn#-(5I--B`MG6itOP6;MrU*A+&6Q-9T8&*NIfd7c=X1Rs#@tG zojYUWAIbd1{fDfxoU$60c)|`}l{HF$=m3Wb2||Qu)iV?9#Ps zXSg3-U{COeBJ$0qh*;ko7H@Ql^7S57Z9qu_fK~vw870v=U*}dK%2)t0i(_0Yz^Y^? zlkRjdKBt{c@g#C7J}2w(B{LqMlXdxAoKqv9z5HJH@iUhqj~RR>oeh_`bms8|KiW`4 zH7aQlg&&S1^jUy5?9c*V-AH8d6Qr#m9=gdOtWL#a8I~ui6oH1S--#c+`?J55<$fe3 znM9_s5<78_*gn3BEX!`n&(9x?1R?Rt{BEq;Fsy}5G>BHwlVYl$J84DwZv#6D^Leiw zw6LsF88cNd$TCA-Bxe&y)4!K@k=$=SP5pP>uSosfgypT2<1HN83#q>gx|?gDt*)%5 zrRM*RcQ;-AS7ccXL%)lr3Y(loQ&`MeF7wso37Vg8Dl9BKnwXe4EP<-Au@NdNDs*;7 zl=Kb9gEZS8N`yD*cc$MkpKlR-j@@@`T=J;%tLYxg9f?2$7AP_vL{-%!1$d_wnvb3p zN$R4zFW$9rZzjX?(BB<`&V~z8+to8?JL=E0zM|-E??unkH z8LX9Gm+%$ux}+tkYaBVLep|o|oYRVgPPn%3cqQJPo|H28DNCZy+ZC)nA&Y${q6+!V zi9?%#4~ym&$H*Uk=3~qxIXkK#vA?!`f6NrcSWt*{FUzu9GBYy|34+k0 zs;bUzja8zF_5d$I5VWqYE_-cl?HYrQ9P4qD^0{dJlE?KHlF6R4UbY|66i=9x2R{!Manau&<738QRythcFn zNWeJ288rGv&Dprh%42m_QI!79&dv)>O-)!NgLP;Fk(I;YIJ9ipvPWDltZ6Yga#Om0e+UFe|9q2uu4~N4$3D5K2UA1b}FI+Ac7MbWO$1seBXJ(jSDT;#b zQwP&1s>O3P^jX9c5fnv0XhYNc&$ZQPkNXSNc*-+v<~CnpWP1P&Sx z6p=(0s$h+G7b>yO4-w=PruRK-Tkn!pj?uXk?~GuW@Z{9)X~vFOG`_?t*Yva zI1LQLd?zn2?_co15=Bu4>K(q|7mkzXd2;2-m2L!x1_A+$GpNO4!N3gl59$9`00513 znB%x7i;Ig7*lac^DJl7|rKJU#@~DFPvLr1nt#aA2Wx0|hVYD|T26Q?xj{>MG8Xyn^ zfwr`?U_f@4QQx315N{nQvcIsfaJSWJh4S+9vyF|7$bKKZ{C@w%rAwD)ehC56B`8|O zbO|UgFFzd$h5i}ot^#ioH=uxXuz->yQHM#xkx1PGc?=@2?;w_u3WjpZnvZA ze&MCZa73S~SbBPTU2bl!ACJRA3vJqv=H_NO5{b0CT&|fOj|WeRGCdgd_xD366zV_| zG-Jk$WIWgB%VeZc5YPsdmX@Bw0^3JduU`F2kH@2$ZXWFl&-11mrwC4TaED<4%MN6l zj3c&U)=o^P4VmDx(9L5ZA5&Ud`j@)PvD@wcl$)FTeYYD|8H0i1V2WUbKyk#+C)mNy z_3IetwY9b3QDD#Bs?gqRuEj7A+?1M{`nbpAS;}#o4P&0qwWrhka6$-`Wx21ty}h!f irRCtQmHk_2LjE5rPxBbF;;cXb0000 literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b79bb8a35cc66c3c1fd44f5a5526c1b78be..e04244066a14cfac2aed41a17ff9df209b7d1fa9 100644 GIT binary patch literal 2843 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 tf8i?U$|WSs4-E}i*pfB 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 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d34e7a88e3f88bea192c3a370d44689c3c..c6897d67135b9a58dde950d7504e574820259d1e 100644 GIT binary patch literal 8938 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 literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372eebdb28e45604e46eeda8dd24651419bc0..3b667db74711186948cae8ceb69b1d0d90886a45 100644 GIT binary patch literal 12858 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 diff --git a/lib/components/location_selector.dart b/lib/components/location_selector.dart index 241e474..582f68d 100644 --- a/lib/components/location_selector.dart +++ b/lib/components/location_selector.dart @@ -19,7 +19,7 @@ class LocationSelector extends StatefulWidget { double? width; double? iconSize; double? searchSize; - Function(Coordinates) submitData; + Function(Coordinates,String) submitData; @override State createState() => _LocationSelectorState(); @@ -356,7 +356,7 @@ class _LocationSelectorState extends State { )); } } - widget.submitData(point); + widget.submitData(point,label); } @@ -368,7 +368,7 @@ class _LocationSelectorState extends State { print(out); if (out.isNotEmpty) { widget.submitData( - Coordinates(latitude: out[0].latitude, longitude: out[0].longitude)); + Coordinates(latitude: out[0].latitude, longitude: out[0].longitude), label); } else { if (!Get.isSnackbarOpen) { diff --git a/lib/components/navisland.dart b/lib/components/navisland.dart index 3444ca4..600063c 100644 --- a/lib/components/navisland.dart +++ b/lib/components/navisland.dart @@ -153,15 +153,9 @@ class NavIsland extends StatelessWidget { color: Colors.transparent, child: InkWell( onTap: () async { - /*LatLng centerPoint = LatLng(37.7749, -122.4194); // Example center point (San Francisco) - double length = 120.0; // Length of the footprint in meters - double width = 70.0; // Width of the footprint in meters - double angle = 0 * (Math.pi / 180); // Angle of rotation in radians (45 degrees in this case) - String kmlOutput = KMLGenerator.generateFootprintBottomPolygon(centerPoint, length, width, angle); - print(kmlOutput);*/ - LatLng start = LatLng(1,6); - LatLng end = LatLng(5, 3); + LatLng start = LatLng(1,5); + LatLng end = LatLng(7,11); double dashLength = 10000.0; // Length of each dash in meters double gapLength = 5000.0; // Length of gap between dashes in meters diff --git a/lib/data_class/coordinate.dart b/lib/data_class/coordinate.dart index 6a93da2..9ae1ef0 100644 --- a/lib/data_class/coordinate.dart +++ b/lib/data_class/coordinate.dart @@ -1,11 +1,17 @@ +import 'package:latlong2/latlong.dart'; + class Coordinates { - double latitude; - double longitude; + late double latitude; + late double longitude; Coordinates({ required this.latitude, required this.longitude, }); + Coordinates.fromLatLng(LatLng point) { + latitude = point.latitude; + longitude = point.longitude; + } // Override toString for better debugging output @override diff --git a/lib/data_class/geo_reversecode_response.dart b/lib/data_class/geo_reversecode_response.dart new file mode 100644 index 0000000..436e8d1 --- /dev/null +++ b/lib/data_class/geo_reversecode_response.dart @@ -0,0 +1,268 @@ +class GeoReverseCodeResponse { + List? results; + Query? query; + + GeoReverseCodeResponse({this.results, this.query}); + + GeoReverseCodeResponse.fromJson(Map json) { + this.results = json["results"] == null ? null : (json["results"] as List).map((e) => Results.fromJson(e)).toList(); + this.query = json["query"] == null ? null : Query.fromJson(json["query"]); + } + + Map toJson() { + final Map data = new Map(); + if (this.results != null) + data["results"] = this.results?.map((e) => e.toJson()).toList(); + if (this.query != null) + data["query"] = this.query?.toJson(); + return data; + } + + @override + String toString() { + return 'GeoReverseCodeResponse{results: $results, query: $query}'; + } +} + +class Query { + double? lat; + double? lon; + String? plusCode; + + Query({this.lat, this.lon, this.plusCode}); + + Query.fromJson(Map json) { + this.lat = (json["lat"])?.toDouble(); + this.lon = (json["lon"])?.toDouble(); + this.plusCode = json["plus_code"]; + } + + Map toJson() { + final Map data = new Map(); + data["lat"] = this.lat; + data["lon"] = this.lon; + data["plus_code"] = this.plusCode; + return data; + } + + @override + String toString() { + return 'Query{lat: $lat, lon: $lon, plusCode: $plusCode}'; + } +} + +class Results { + Datasource? datasource; + String? country; + String? countryCode; + double? lon; + double? lat; + double? distance; + String? resultType; + String? formatted; + String? addressLine1; + String? addressLine2; + String? category; + Timezone? timezone; + String? plusCode; + Rank? rank; + String? placeId; + Bbox? bbox; + + Results( + {this.datasource, + this.country, + this.countryCode, + this.lon, + this.lat, + this.distance, + this.resultType, + this.formatted, + this.addressLine1, + this.addressLine2, + this.category, + this.timezone, + this.plusCode, + this.rank, + this.placeId, + this.bbox}); + + Results.fromJson(Map json) { + this.datasource = json["datasource"] == null ? null : Datasource.fromJson(json["datasource"]); + this.country = json["country"]; + this.countryCode = json["country_code"]; + this.lon = json["lon"]; + this.lat = json["lat"]; + this.distance = json["distance"]?.toDouble(); + this.resultType = json["result_type"]; + this.formatted = json["formatted"]; + this.addressLine1 = json["address_line1"]; + this.addressLine2 = json["address_line2"]; + this.category = json["category"]; + this.timezone = json["timezone"] == null ? null : Timezone.fromJson(json["timezone"]); + this.plusCode = json["plus_code"]; + this.rank = json["rank"] == null ? null : Rank.fromJson(json["rank"]); + this.placeId = json["place_id"]; + this.bbox = json["bbox"] == null ? null : Bbox.fromJson(json["bbox"]); + } + + Map toJson() { + final Map data = new Map(); + if (this.datasource != null) + data["datasource"] = this.datasource?.toJson(); + data["country"] = this.country; + data["country_code"] = this.countryCode; + data["lon"] = this.lon; + data["lat"] = this.lat; + data["distance"] = this.distance; + data["result_type"] = this.resultType; + data["formatted"] = this.formatted; + data["address_line1"] = this.addressLine1; + data["address_line2"] = this.addressLine2; + data["category"] = this.category; + if (this.timezone != null) + data["timezone"] = this.timezone?.toJson(); + data["plus_code"] = this.plusCode; + if (this.rank != null) + data["rank"] = this.rank?.toJson(); + data["place_id"] = this.placeId; + if (this.bbox != null) + data["bbox"] = this.bbox?.toJson(); + return data; + } + + @override + String toString() { + return 'Results{datasource: $datasource, country: $country, countryCode: $countryCode, lon: $lon, lat: $lat, distance: $distance, resultType: $resultType, formatted: $formatted, addressLine1: $addressLine1, addressLine2: $addressLine2, category: $category, timezone: $timezone, plusCode: $plusCode, rank: $rank, placeId: $placeId, bbox: $bbox}'; + } +} + +class Bbox { + double? lon1; + double? lat1; + double? lon2; + double? lat2; + + Bbox({this.lon1, this.lat1, this.lon2, this.lat2}); + + Bbox.fromJson(Map json) { + this.lon1 = json["lon1"]?.toDouble(); + this.lat1 = json["lat1"]?.toDouble(); + this.lon2 = json["lon2"]?.toDouble(); + this.lat2 = json["lat2"]?.toDouble(); + } + + Map toJson() { + final Map data = new Map(); + data["lon1"] = this.lon1; + data["lat1"] = this.lat1; + data["lon2"] = this.lon2; + data["lat2"] = this.lat2; + return data; + } + + @override + String toString() { + return 'Bbox{lon1: $lon1, lat1: $lat1, lon2: $lon2, lat2: $lat2}'; + } +} + +class Rank { + double? importance; + double? popularity; + + Rank({this.importance, this.popularity}); + + Rank.fromJson(Map json) { + this.importance = json["importance"]; + this.popularity = json["popularity"]; + } + + Map toJson() { + final Map data = new Map(); + data["importance"] = this.importance; + data["popularity"] = this.popularity; + return data; + } + + @override + String toString() { + return 'Rank{importance: $importance, popularity: $popularity}'; + } +} + +class Timezone { + String? name; + String? offsetStd; + int? offsetStdSeconds; + String? offsetDst; + int? offsetDstSeconds; + String? abbreviationStd; + String? abbreviationDst; + + Timezone( + {this.name, + this.offsetStd, + this.offsetStdSeconds, + this.offsetDst, + this.offsetDstSeconds, + this.abbreviationStd, + this.abbreviationDst}); + + Timezone.fromJson(Map json) { + this.name = json["name"]; + this.offsetStd = json["offset_STD"]; + this.offsetStdSeconds = json["offset_STD_seconds"]; + this.offsetDst = json["offset_DST"]; + this.offsetDstSeconds = json["offset_DST_seconds"]; + this.abbreviationStd = json["abbreviation_STD"]; + this.abbreviationDst = json["abbreviation_DST"]; + } + + Map toJson() { + final Map data = new Map(); + data["name"] = this.name; + data["offset_STD"] = this.offsetStd; + data["offset_STD_seconds"] = this.offsetStdSeconds; + data["offset_DST"] = this.offsetDst; + data["offset_DST_seconds"] = this.offsetDstSeconds; + data["abbreviation_STD"] = this.abbreviationStd; + data["abbreviation_DST"] = this.abbreviationDst; + return data; + } + + @override + String toString() { + return 'Timezone{name: $name, offsetStd: $offsetStd, offsetStdSeconds: $offsetStdSeconds, offsetDst: $offsetDst, offsetDstSeconds: $offsetDstSeconds, abbreviationStd: $abbreviationStd, abbreviationDst: $abbreviationDst}'; + } +} + +class Datasource { + String? sourcename; + String? attribution; + String? license; + String? url; + + Datasource({this.sourcename, this.attribution, this.license, this.url}); + + Datasource.fromJson(Map json) { + this.sourcename = json["sourcename"]; + this.attribution = json["attribution"]; + this.license = json["license"]; + this.url = json["url"]; + } + + Map toJson() { + final Map data = new Map(); + data["sourcename"] = this.sourcename; + data["attribution"] = this.attribution; + data["license"] = this.license; + data["url"] = this.url; + return data; + } + + @override + String toString() { + return 'Datasource{sourcename: $sourcename, attribution: $attribution, license: $license, url: $url}'; + } +} diff --git a/lib/data_class/geocode_response.dart b/lib/data_class/geocode_response.dart new file mode 100644 index 0000000..b8277ca --- /dev/null +++ b/lib/data_class/geocode_response.dart @@ -0,0 +1,307 @@ +class GeoCodeResponse { + List? results; + Query? query; + + GeoCodeResponse({this.results, this.query}); + + GeoCodeResponse.fromJson(Map json) { + this.results = json["results"] == null ? null : (json["results"] as List).map((e) => Results.fromJson(e)).toList(); + this.query = json["query"] == null ? null : Query.fromJson(json["query"]); + } + + Map toJson() { + final Map data = new Map(); + if (this.results != null) + data["results"] = this.results?.map((e) => e.toJson()).toList(); + if (this.query != null) + data["query"] = this.query?.toJson(); + return data; + } + + @override + String toString() { + return 'GeoCodeResponse{results: $results, query: $query}'; + } +} + +class Query { + String? text; + Parsed? parsed; + + Query({this.text, this.parsed}); + + Query.fromJson(Map json) { + this.text = json["text"]; + this.parsed = json["parsed"] == null ? null : Parsed.fromJson(json["parsed"]); + } + + Map toJson() { + final Map data = new Map(); + data["text"] = this.text; + if (this.parsed != null) + data["parsed"] = this.parsed?.toJson(); + return data; + } + + @override + String toString() { + return 'Query{text: $text, parsed: $parsed}'; + } +} + +class Parsed { + String? state; + String? country; + String? expectedType; + + Parsed({this.state, this.country, this.expectedType}); + + Parsed.fromJson(Map json) { + this.state = json["state"]; + this.country = json["country"]; + this.expectedType = json["expected_type"]; + } + + Map toJson() { + final Map data = new Map(); + data["state"] = this.state; + data["country"] = this.country; + data["expected_type"] = this.expectedType; + return data; + } + + @override + String toString() { + return 'Parsed{state: $state, country: $country, expectedType: $expectedType}'; + } +} + +class Results { + Datasource? datasource; + String? ref; + String? country; + String? countryCode; + String? state; + double? lon; + double? lat; + String? stateCode; + String? resultType; + String? formatted; + String? addressLine1; + String? addressLine2; + String? category; + Timezone? timezone; + String? plusCode; + Rank? rank; + String? placeId; + Bbox? bbox; + + Results( + {this.datasource, + this.ref, + this.country, + this.countryCode, + this.state, + this.lon, + this.lat, + this.stateCode, + this.resultType, + this.formatted, + this.addressLine1, + this.addressLine2, + this.category, + this.timezone, + this.plusCode, + this.rank, + this.placeId, + this.bbox}); + + Results.fromJson(Map json) { + this.datasource = json["datasource"] == null ? null : Datasource.fromJson(json["datasource"]); + this.ref = json["ref"]; + this.country = json["country"]; + this.countryCode = json["country_code"]; + this.state = json["state"]; + this.lon = json["lon"]; + this.lat = json["lat"]; + this.stateCode = json["state_code"]; + this.resultType = json["result_type"]; + this.formatted = json["formatted"]; + this.addressLine1 = json["address_line1"]; + this.addressLine2 = json["address_line2"]; + this.category = json["category"]; + this.timezone = json["timezone"] == null ? null : Timezone.fromJson(json["timezone"]); + this.plusCode = json["plus_code"]; + this.rank = json["rank"] == null ? null : Rank.fromJson(json["rank"]); + this.placeId = json["place_id"]; + this.bbox = json["bbox"] == null ? null : Bbox.fromJson(json["bbox"]); + } + + Map toJson() { + final Map data = new Map(); + if (this.datasource != null) + data["datasource"] = this.datasource?.toJson(); + data["ref"] = this.ref; + data["country"] = this.country; + data["country_code"] = this.countryCode; + data["state"] = this.state; + data["lon"] = this.lon; + data["lat"] = this.lat; + data["state_code"] = this.stateCode; + data["result_type"] = this.resultType; + data["formatted"] = this.formatted; + data["address_line1"] = this.addressLine1; + data["address_line2"] = this.addressLine2; + data["category"] = this.category; + if (this.timezone != null) + data["timezone"] = this.timezone?.toJson(); + data["plus_code"] = this.plusCode; + if (this.rank != null) + data["rank"] = this.rank?.toJson(); + data["place_id"] = this.placeId; + if (this.bbox != null) + data["bbox"] = this.bbox?.toJson(); + return data; + } + + @override + String toString() { + return 'Results{datasource: $datasource, ref: $ref, country: $country, countryCode: $countryCode, state: $state, lon: $lon, lat: $lat, stateCode: $stateCode, resultType: $resultType, formatted: $formatted, addressLine1: $addressLine1, addressLine2: $addressLine2, category: $category, timezone: $timezone, plusCode: $plusCode, rank: $rank, placeId: $placeId, bbox: $bbox}'; + } +} + +class Bbox { + double? lon1; + double? lat1; + double? lon2; + double? lat2; + + Bbox({this.lon1, this.lat1, this.lon2, this.lat2}); + + Bbox.fromJson(Map json) { + this.lon1 = json["lon1"]; + this.lat1 = json["lat1"]; + this.lon2 = json["lon2"]; + this.lat2 = json["lat2"]; + } + + Map toJson() { + final Map data = new Map(); + data["lon1"] = this.lon1; + data["lat1"] = this.lat1; + data["lon2"] = this.lon2; + data["lat2"] = this.lat2; + return data; + } + + @override + String toString() { + return 'Bbox{lon1: $lon1, lat1: $lat1, lon2: $lon2, lat2: $lat2}'; + } +} + +class Rank { + double? importance; + double? popularity; + int? confidence; + String? matchType; + + Rank({this.importance, this.popularity, this.confidence, this.matchType}); + + Rank.fromJson(Map json) { + this.importance = json["importance"]; + this.popularity = json["popularity"]; + this.confidence = json["confidence"]; + this.matchType = json["match_type"]; + } + + Map toJson() { + final Map data = new Map(); + data["importance"] = this.importance; + data["popularity"] = this.popularity; + data["confidence"] = this.confidence; + data["match_type"] = this.matchType; + return data; + } + + @override + String toString() { + return 'Rank{importance: $importance, popularity: $popularity, confidence: $confidence, matchType: $matchType}'; + } +} + +class Timezone { + String? name; + String? offsetStd; + int? offsetStdSeconds; + String? offsetDst; + int? offsetDstSeconds; + String? abbreviationStd; + String? abbreviationDst; + + Timezone( + {this.name, + this.offsetStd, + this.offsetStdSeconds, + this.offsetDst, + this.offsetDstSeconds, + this.abbreviationStd, + this.abbreviationDst}); + + Timezone.fromJson(Map json) { + this.name = json["name"]; + this.offsetStd = json["offset_STD"]; + this.offsetStdSeconds = json["offset_STD_seconds"]; + this.offsetDst = json["offset_DST"]; + this.offsetDstSeconds = json["offset_DST_seconds"]; + this.abbreviationStd = json["abbreviation_STD"]; + this.abbreviationDst = json["abbreviation_DST"]; + } + + Map toJson() { + final Map data = new Map(); + data["name"] = this.name; + data["offset_STD"] = this.offsetStd; + data["offset_STD_seconds"] = this.offsetStdSeconds; + data["offset_DST"] = this.offsetDst; + data["offset_DST_seconds"] = this.offsetDstSeconds; + data["abbreviation_STD"] = this.abbreviationStd; + data["abbreviation_DST"] = this.abbreviationDst; + return data; + } + + @override + String toString() { + return 'Timezone{name: $name, offsetStd: $offsetStd, offsetStdSeconds: $offsetStdSeconds, offsetDst: $offsetDst, offsetDstSeconds: $offsetDstSeconds, abbreviationStd: $abbreviationStd, abbreviationDst: $abbreviationDst}'; + } +} + +class Datasource { + String? sourcename; + String? attribution; + String? license; + String? url; + + Datasource({this.sourcename, this.attribution, this.license, this.url}); + + Datasource.fromJson(Map json) { + this.sourcename = json["sourcename"]; + this.attribution = json["attribution"]; + this.license = json["license"]; + this.url = json["url"]; + } + + Map toJson() { + final Map data = new Map(); + data["sourcename"] = this.sourcename; + data["attribution"] = this.attribution; + data["license"] = this.license; + data["url"] = this.url; + return data; + } + + @override + String toString() { + return 'Datasource{sourcename: $sourcename, attribution: $attribution, license: $license, url: $url}'; + } +} diff --git a/lib/main.dart b/lib/main.dart index d1b846b..443ccbc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,14 +18,7 @@ AndroidMapRenderer mapRenderer = AndroidMapRenderer.platformDefault; void main() { WidgetsFlutterBinding.ensureInitialized(); - /*final GoogleMapsFlutterPlatform mapsImplementation = - GoogleMapsFlutterPlatform.instance; - if (mapsImplementation is GoogleMapsFlutterAndroid) { - // Force Hybrid Composition mode. - mapsImplementation.useAndroidViewSurface = true; - mapRenderer = await mapsImplementation - .initializeWithRenderer(AndroidMapRenderer.latest); - }*/ + Get.lazyPut(() => LGConnection(),fenix: true); Get.lazyPut(() => ApiManager(),fenix: true); Get.lazyPut(() => SpeechController(),fenix: true); diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index dac247d..4f8e128 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -104,7 +104,7 @@ class _DashBoardState extends State { width: screenHeight * 0.1, child: Icon( Icons.settings, - size: 60, + size: screenHeight*0.07, color: Colors.white, ), onTap: () async { diff --git a/lib/screens/kml_builder.dart b/lib/screens/kml_builder.dart index c5ccc31..dba08c4 100644 --- a/lib/screens/kml_builder.dart +++ b/lib/screens/kml_builder.dart @@ -14,7 +14,8 @@ import 'package:super_liquid_galaxy_controller/screens/test.dart'; import 'package:super_liquid_galaxy_controller/utils/galaxy_colors.dart'; import 'package:super_liquid_galaxy_controller/utils/kmlgenerator.dart'; import 'package:super_liquid_galaxy_controller/utils/lg_connection.dart'; - +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; import '../data_class/kml_element.dart'; import '../data_class/map_position.dart'; import '../generated/assets.dart'; @@ -141,7 +142,7 @@ class _KmlUploaderState extends State { fontWeight: FontWeight.w400, fontSize: 25.0), ), - Divider( + const Divider( thickness: 1.0, ), const SizedBox( @@ -252,6 +253,8 @@ class _KmlUploaderState extends State { //await sshClient.clearKml(); File? file = await sshClient.makeFile(filename, KMLGenerator.generateCustomKml('slave_1', kmlList)); + + //Get.to(()=>TestScreen(kml: KMLGenerator.generateCustomKml('slave_1', kmlList))); //String kml = KMLGenerator.generateCustomKml('slave_1', kmlList); print("made successfully"); await sshClient.kmlFileUpload( @@ -266,11 +269,15 @@ class _KmlUploaderState extends State { GalaxyButton( height: screenHeight * 0.1, width: screenWidth * 0.31, - actionText: "VISUALIZE IN MAP", - icon: Icons.map, + actionText: "DOWNLOAD KML", + icon: Icons.save_alt_rounded, isLeading: true, onTap: () async { - await sshClient.clearKml(); + //await sshClient.clearKml(); + String kml = KMLGenerator.generateCustomKml('slave_1', kmlList); + saveStringToExternalStorageWithProgress(kml, 'custom_kml', 'kml', (progress){ + print(progress); + }); }, backgroundColor: GalaxyColors.blue.withOpacity(0.4), ) @@ -444,4 +451,52 @@ class _KmlUploaderState extends State { } } } + Future saveStringToExternalStorageWithProgress( + String content, String filename, String extension, Function(double) onProgress) async { + // Request storage permissions + if (await Permission.manageExternalStorage.request().isGranted) { + // Get the external storage directory + Directory? directory = await getExternalStorageDirectory(); + if (directory != null) { + // Create the file path with the custom extension + String path = '${directory.path}/$filename.$extension'; + // Write the string content to the file in chunks + File file = File(path); + RandomAccessFile raf = await file.open(mode: FileMode.write); + int totalLength = content.length; + int chunkSize = 1024; // Write in chunks of 1KB + int writtenLength = 0; + + for (int i = 0; i < totalLength; i += chunkSize) { + int end = (i + chunkSize < totalLength) ? i + chunkSize : totalLength; + await raf.writeString(content.substring(i, end)); + writtenLength += (end - i); + // Calculate and report progress + double progress = writtenLength / totalLength; + onProgress(progress); + } + + await raf.close(); + print('File saved at $path'); + showSuccessSnackbar("File saved at $path"); + } else { + print('External storage directory not found'); + } + } else { + print('Storage permission denied'); + } + + } + + void showSuccessSnackbar(String message) { + if (!Get.isSnackbarOpen) { + Get.showSnackbar(GetSnackBar( + backgroundColor: Colors.green.shade300, + title: "Download Successful", + message: message, + isDismissible: true, + duration: 5.seconds, + )); + } + } } diff --git a/lib/screens/splashscreen.dart b/lib/screens/splashscreen.dart index 937e0ae..d22bbf1 100644 --- a/lib/screens/splashscreen.dart +++ b/lib/screens/splashscreen.dart @@ -13,10 +13,6 @@ class SplashScreen extends StatefulWidget { } class _SplashScreenState extends State { - - - - @override Widget build(BuildContext context) { return AnimatedSplashScreen.withScreenFunction( diff --git a/lib/screens/tour_builder.dart b/lib/screens/tour_builder.dart index e072c7a..958e994 100644 --- a/lib/screens/tour_builder.dart +++ b/lib/screens/tour_builder.dart @@ -27,7 +27,7 @@ class _TourBuilderState extends State { @override void initState() { tourController = Get.find(); - _determinePosition(); + //_determinePosition(); super.initState(); } @@ -104,7 +104,7 @@ class _TourBuilderState extends State { Colors.grey.withOpacity(0.2) ]), child: ImageIcon( - AssetImage(Assets.iconsSshIndicator), + const AssetImage(Assets.iconsSshIndicator), color: tourController.connectionClient.isConnected.value ? Colors.green @@ -125,7 +125,7 @@ class _TourBuilderState extends State { Colors.grey.withOpacity(0.2), ]), child: ImageIcon( - AssetImage(Assets.iconsApiIndicator), + const AssetImage(Assets.iconsApiIndicator), color: tourController.apiClient.isConnected.value ? Colors.green : Colors.red, @@ -183,8 +183,8 @@ class _TourBuilderState extends State { width: screenWidth * 0.25, iconSize: screenHeight * 0.1, searchSize: screenWidth * 0.85, - submitData: (Coordinates point) { - setSearchAround(point); + submitData: (Coordinates point, String label) { + setSearchAround(point,label); }, ) ], @@ -265,13 +265,13 @@ class _TourBuilderState extends State { //_getCity(locator.latitude, locator.longitude); setState(() { setSearchAround( - Coordinates(latitude: point.latitude, longitude: point.longitude)); + Coordinates(latitude: point.latitude, longitude: point.longitude),'Default'); }); } - void setSearchAround(Coordinates point) { + void setSearchAround(Coordinates point, String label) { setState(() { - tourController.setSearchAround(point); + tourController.setSearchAround(point,label); }); print(tourController.getSearchAround()); } diff --git a/lib/utils/api_manager.dart b/lib/utils/api_manager.dart index bfeba9c..219390a 100644 --- a/lib/utils/api_manager.dart +++ b/lib/utils/api_manager.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart' as getx; import 'package:shared_preferences/shared_preferences.dart'; import 'package:super_liquid_galaxy_controller/data_class/api_error.dart'; +import 'package:super_liquid_galaxy_controller/data_class/coordinate.dart'; +import 'package:super_liquid_galaxy_controller/data_class/geo_reversecode_response.dart'; class ApiManager extends getx.GetxController { late String _placesApiKey; @@ -17,6 +19,8 @@ class ApiManager extends getx.GetxController { //end-points static const autocompleteEndPoint = "/geocode/autocomplete"; static const placesEndPoint = "/places"; + static const geocodeEndPoint = "/geocode/search"; + static const reverseGeoCodeEndPoint = "/geocode/reverse"; /*ApiManager._privateConstructor() { print("instance created"); @@ -60,6 +64,45 @@ class ApiManager extends getx.GetxController { return response; } + Future getGeoCodeResponse(String text, String searchLevel) async { + await _connectApi(1); + var response = await _apiClient.get(geocodeEndPoint, queryParameters: { + 'text': text, + 'apiKey': _placesApiKey.trim(), + 'format': 'json', + 'type': searchLevel + }); + if (response.statusCode != 200) { + handleError(response); + } + return response; + } + + Future getReverseGeoCodeResponse( + Coordinates point, String searchLevel) async { + await _connectApi(1); + bool hasType = searchLevel.compareTo("none") != 0; + print("hasType: $hasType"); + var response = hasType + ? await _apiClient.get(reverseGeoCodeEndPoint, queryParameters: { + 'lat': point.latitude.toString(), + 'lon': point.longitude.toString(), + 'apiKey': _placesApiKey.trim(), + 'format': 'json', + 'type': searchLevel + }) + : await _apiClient.get(reverseGeoCodeEndPoint, queryParameters: { + 'lat': point.latitude, + 'lon': point.longitude, + 'apiKey': _placesApiKey.trim(), + 'format': 'json' + }); + if (response.statusCode != 200) { + handleError(response); + } + return response; + } + void handleError(Response response) { var errorResponse = ApiErrorResponse.fromJson(response.data); print('error : ${response.statusMessage}'); @@ -77,25 +120,49 @@ class ApiManager extends getx.GetxController { testApiKey() async { await _connectApi(1); try { - var response = await _apiClient.get( - autocompleteEndPoint, - queryParameters: {'text': "test", - 'apiKey': _placesApiKey.trim() - }); + var response = await _apiClient.get(autocompleteEndPoint, + queryParameters: {'text': "test", 'apiKey': _placesApiKey.trim()}); if (response.statusCode == 200) { isConnected.value = true; - } - else { + } else { var error = ApiErrorResponse.fromJson(response.data); isConnected.value = false; print('${error.error} - ${error.message}'); } + } catch (e) { + print(e); + isConnected.value = false; } - catch(e) - { - print(e); - isConnected.value = false; + } + + Future tryResponseFromPoint(Coordinates searchAroundCoords, bool isCountry) async { + Response response = await getReverseGeoCodeResponse( + searchAroundCoords, isCountry ? 'country' : 'state'); + if (response.statusCode != 200) { + return ''; + } + var responseObj = GeoReverseCodeResponse.fromJson(response.data); + + if (responseObj.results != null && responseObj.results!.isNotEmpty) { + return responseObj.results![0].placeId!; + } + else + { + Response response2 = await getReverseGeoCodeResponse( + searchAroundCoords, 'none'); + var responseObj2 = GeoReverseCodeResponse.fromJson(response2.data); + print(responseObj2); + if (response2.statusCode != 200) { + return ''; + } + if (responseObj2.results != null && responseObj2.results!.isNotEmpty) { + return responseObj2.results![0].placeId!; } + else + { + return ''; + } + } } } diff --git a/lib/utils/kmlgenerator.dart b/lib/utils/kmlgenerator.dart index d6ae23e..2da7705 100644 --- a/lib/utils/kmlgenerator.dart +++ b/lib/utils/kmlgenerator.dart @@ -1,8 +1,10 @@ +import 'dart:math' as Math; +import 'dart:math'; + +import 'package:latlong2/latlong.dart'; import 'package:super_liquid_galaxy_controller/data_class/coordinate.dart'; import 'package:super_liquid_galaxy_controller/data_class/kml_element.dart'; import 'package:super_liquid_galaxy_controller/utils/geo_utils.dart'; -import 'package:latlong2/latlong.dart'; -import 'dart:math' as Math; class KMLGenerator { static String generateBlank(String id) { @@ -25,14 +27,11 @@ class KMLGenerator { '''; - - static getCoordinateList(List list) - { + static getCoordinateList(List list) { var coordinates = ''; - for(final coordinate in list) - { - coordinates += '${coordinate.longitude},${coordinate.latitude},0 '; - } + for (final coordinate in list) { + coordinates += '${coordinate.longitude},${coordinate.latitude},0 '; + } return '''${coordinates}'''; } @@ -68,37 +67,37 @@ class KMLGenerator { var visList = ''; List coordsList = []; for (final item in list) { - switch (item.index) { case 0: { - Placemark element = item.elementData; - visList+=getPlacemark(element); - coordsList.add(element.coordinate); + Placemark element = item.elementData; + visList += getPlacemark(element); + coordsList.add(element.coordinate); } case 1: { LineString element = item.elementData; - visList+=getLineString(element); + visList += getLineString(element); coordsList.addAll(element.coordinates); } case 2: { PolyGon element = item.elementData; - visList+=getLinearRing(element); + element.coordinates.add(element.coordinates[0]); + visList += getLinearRing(element); coordsList.addAll(element.coordinates); } default: { Placemark element = item.elementData; - visList+=getPlacemark(element); + visList += getPlacemark(element); coordsList.add(element.coordinate); } } } - var lookAt =''; - lookAt+= GeoUtils.calculateLookAt(coordsList,45); + var lookAt = ''; + lookAt += GeoUtils.calculateLookAt(coordsList, 45); return ''' @@ -127,6 +126,7 @@ class KMLGenerator { '''; } + static String generateKml(String id, String kml) { return ''' @@ -141,6 +141,12 @@ class KMLGenerator { 7f00ff00 + + + 1 + + ${start.longitude},${start.latitude},0 + ${end.longitude},${end.latitude},0 + + + + ''');*/ + + var bearing = distance.bearing(start, end); + LatLng roadStart1 = + distance.offset(start, gapLength, bearing - 90); + LatLng roadStart2 = + distance.offset(start, gapLength, bearing + 90); + LatLng roadEnd1 = + distance.offset(end, gapLength, bearing + 90); + LatLng roadEnd2 = + distance.offset(end, gapLength, bearing - 90); + kmlSegments.add(''' + + + + + 1 + + ${getCoordinateList([ + Coordinates.fromLatLng(roadEnd1), + Coordinates.fromLatLng(roadEnd2), + Coordinates.fromLatLng(roadStart1), + Coordinates.fromLatLng(roadStart2), + Coordinates.fromLatLng(roadEnd1), + ])} + + + + + '''); + for (int i = 0; i < numSegments; i++) { // Calculate the start point of the dash - LatLng dashStart = distance.offset(start, i * segmentLength, distance.bearing(start, end)); + LatLng dashStart = distance.offset( + start, i * segmentLength, distance.bearing(start, end)); // Calculate the end point of the dash - LatLng dashEnd = distance.offset(dashStart, dashLength, distance.bearing(start, end)); - - + LatLng dashEnd = + distance.offset(dashStart, dashLength, distance.bearing(start, end)); kmlSegments.add(''' + #rs ${dashStart.longitude},${dashStart.latitude},0 @@ -382,12 +449,59 @@ class KMLGenerator { return generateKml('69', kml); } + static double smoothCurve(double x) { + // Ensure x is within the range [0, 1] + if (x < 0) return 0; + if (x > 1) return 0; + return sin(pi * x); + } + static String generateAirplaneTrack( + LatLng start, LatLng end, double dashLength, double gapLength) { + final Distance distance = Distance(); + final double totalDistance = distance(start, end); + final double segmentLength = dashLength + gapLength; + final int numSegments = (totalDistance / segmentLength).floor(); + + List kmlSegments = []; + const double maxHeight = 500000.0; + var stepCount = numSegments*2; + var stepSize = 1.0/stepCount; + var steps = 0.0; + // var altitude = 0.0; + for (int i = 0; i < numSegments; i++) { + // Calculate the start point of the dash + LatLng dashStart = distance.offset( + start, i * segmentLength, distance.bearing(start, end)); + // Calculate the end point of the dash + LatLng dashEnd = + distance.offset(dashStart, dashLength, distance.bearing(start, end)); + var altitude1 = smoothCurve(steps)*maxHeight; + steps+=stepSize; + var altitude2 = smoothCurve(steps)*maxHeight; + steps+=stepSize; + kmlSegments.add(''' + + + relativeToGround + + ${dashStart.longitude},${dashStart.latitude},$altitude1 + ${dashEnd.longitude},${dashEnd.latitude},$altitude2 + + + + '''); + } + String kml = ''; + kml += kmlSegments.join(); + //kml += ''; + return generateKml('69', kml); + } - static generateFootprints(LatLng start, LatLng end, double dashLength, double gapLength) - { + static generateFootprints( + LatLng start, LatLng end, double dashLength, double gapLength) { final Distance distance = Distance(); final double totalDistance = distance(start, end); final double segmentLength = dashLength + gapLength; @@ -398,18 +512,21 @@ class KMLGenerator { List> pointList = []; List kmlSegments = []; - for(int i =0;i ellipsePoints = []; @@ -429,18 +550,22 @@ class KMLGenerator { print("kml: top point -$topPoint"); print("kml: bottom point -$bottomPoint"); - for (int i = 0; i <=360; i += 5) { + for (int i = 0; i <= 360; i += 5) { double theta = i * (Math.pi / 180); - double rotatedX = (length/2.0) * Math.cos(theta); - double rotatedY = (width/2.0) * Math.sin(theta); + double rotatedX = (length / 2.0) * Math.cos(theta); + double rotatedY = (width / 2.0) * Math.sin(theta); // double rotatedX = x * Math.cos(angle) - y * Math.sin(angle); // double rotatedY = x * Math.sin(angle) + y * Math.cos(angle); - LatLng point = distance.offset(center, Math.sqrt(rotatedX * rotatedX + rotatedY * rotatedY), + LatLng point = distance.offset( + center, + Math.sqrt(rotatedX * rotatedX + rotatedY * rotatedY), Math.atan2(rotatedY, rotatedX) * (180.0 / Math.pi)); - if(((Math.atan2(rotatedY, rotatedX) * (180.0 / Math.pi) )- angle).abs()<=2) - print("kml: point debug - $point, ${Math.atan2(rotatedY, rotatedX) * (180.0 / Math.pi)}"); + if (((Math.atan2(rotatedY, rotatedX) * (180.0 / Math.pi)) - angle) + .abs() <= + 2) + print( + "kml: point debug - $point, ${Math.atan2(rotatedY, rotatedX) * (180.0 / Math.pi)}"); ellipsePoints.add(point); - } String kml = ''' @@ -466,7 +591,5 @@ class KMLGenerator { '''; return kml; - } - } diff --git a/lib/utils/tour_controller.dart b/lib/utils/tour_controller.dart index fa18d59..042ada6 100644 --- a/lib/utils/tour_controller.dart +++ b/lib/utils/tour_controller.dart @@ -1,30 +1,48 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:dio/dio.dart'; +import 'package:get/get.dart' as getx; +import 'package:super_liquid_galaxy_controller/data_class/geocode_response.dart'; import 'package:super_liquid_galaxy_controller/utils/api_manager.dart'; import 'package:super_liquid_galaxy_controller/utils/lg_connection.dart'; import '../data_class/coordinate.dart'; -class TourController extends GetxController -{ +class TourController extends getx.GetxController { late ApiManager apiClient; late LGConnection connectionClient; var _searchAroundCoords = Coordinates(latitude: 12.0, longitude: 12.0).obs; - + String _label = ''; @override void onInit() { - apiClient = Get.find(); - connectionClient = Get.find(); + apiClient = getx.Get.find(); + connectionClient = getx.Get.find(); super.onInit(); } - void setSearchAround(Coordinates point) - { + void setSearchAround(Coordinates point, String label) { _searchAroundCoords.value = point; + _label = label; + getPlaceBounds(); } - Coordinates getSearchAround() - { + + Coordinates getSearchAround() { return _searchAroundCoords.value; } -} \ No newline at end of file + + void getPlaceBounds() async { + + var queryText = _label.split('\n').reversed.join(", "); + bool isCountry = _label.split('\n').where((String str) { + return str.isNotEmpty; + }).length == 1; + if(isCountry) + { + queryText = _label.split('\n')[0]; + } + print(queryText); + String placeID = await apiClient.tryResponseFromPoint( + _searchAroundCoords.value, isCountry); + print("id: $placeID"); + + } +} diff --git a/pubspec.lock b/pubspec.lock index af61ef5..f1aee90 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + change_app_package_name: + dependency: "direct dev" + description: + name: change_app_package_name + sha256: "494e7943d4e8ba6a70970bf0fac293c866a121c50096dc9e027565bfbfc5c7a1" + url: "https://pub.dev" + source: hosted + version: "1.2.0" characters: dependency: transitive description: @@ -592,6 +600,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.1" + permission_handler: + dependency: "direct dev" + description: + name: permission_handler + sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 + url: "https://pub.dev" + source: hosted + version: "10.4.5" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" + url: "https://pub.dev" + source: hosted + version: "10.3.6" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + url: "https://pub.dev" + source: hosted + version: "9.1.4" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + url: "https://pub.dev" + source: hosted + version: "3.12.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + url: "https://pub.dev" + source: hosted + version: "0.1.3" petitparser: dependency: transitive description: @@ -656,30 +704,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - sealed_countries: - dependency: "direct dev" - description: - name: sealed_countries - sha256: e5c7436ebdc6c3fd332c3bc16831fd4e69d5ade6f3123d33791b6be29fcdb91b - url: "https://pub.dev" - source: hosted - version: "1.5.0" - sealed_currencies: - dependency: transitive - description: - name: sealed_currencies - sha256: d02f259ffa25fa1b227478edd00ffb7bc78860168611be97aab4bcab984b9a69 - url: "https://pub.dev" - source: hosted - version: "1.5.0" - sealed_languages: - dependency: transitive - description: - name: sealed_languages - sha256: "060ef61bf1b42cccdf18d065707ed9a75905fe7b78a6e65aec9a2e876489253e" - url: "https://pub.dev" - source: hosted - version: "1.5.0" shared_preferences: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 511fc2c..cb93734 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + change_app_package_name: ^1.2.0 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -61,8 +62,8 @@ dev_dependencies: path_provider: ^2.1.3 speech_to_text: ^6.6.0 avatar_glow: ^3.0.1 - sealed_countries: ^1.5.0 geocoding: ^3.0.0 + permission_handler: ^10.2.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 94586cc..ce843bc 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,11 +7,14 @@ #include "generated_plugin_registrant.h" #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { GeolocatorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("GeolocatorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index f0bcafd..b3ea692 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST geolocator_windows + permission_handler_windows url_launcher_windows )