diff --git a/lib/screens/about.dart b/lib/screens/about.dart new file mode 100644 index 0000000..328159d --- /dev/null +++ b/lib/screens/about.dart @@ -0,0 +1,135 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:loc/styles/colors.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class About extends StatelessWidget { + const About({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bg, + appBar: AppBar( + backgroundColor: AppColors.pink, + elevation: 0, + foregroundColor: AppColors.fg, + title: const Text( + 'About', + style: TextStyle( + fontSize: 20, + ), + ), + ), + body: Container( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 16, + bottom: 16, + ), + alignment: Alignment.center, + child: Column( + children: [ + const Image( + image: AssetImage('assets/images/app_icon.png'), + width: 150, + height: 150, + ), + const Padding( + padding: EdgeInsets.only( + top: 24, + ), + child: Text( + 'Loc', + textAlign: TextAlign.center, + style: TextStyle( + color: AppColors.fg, + fontSize: 48, + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + top: 16, + bottom: 8, + ), + child: Text( + 'v0.2.2', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + ), + ), + ), + const Padding( + padding: EdgeInsets.only( + top: 8, + bottom: 16, + ), + child: Text( + 'GPL v3 License', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, + ), + ), + ), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: const TextStyle( + color: AppColors.fg, + fontFamily: 'Fantasque', + fontSize: 18, + ), + children: [ + const TextSpan( + text: + 'Free and open-source location-based reminder for Android.\n\n', + ), + const TextSpan( + text: 'Designed by \n\n', + ), + TextSpan( + text: 'Abd El-Twab M. Fakhry\n\n', + style: const TextStyle( + color: AppColors.lavender, + fontSize: 28, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const amf = 'https://abdeltwabmf.github.io/'; + await _launchUrl(amf); + }, + ), + const TextSpan( + text: 'Source Code can be found here on\n\n', + ), + TextSpan( + text: 'GitHub', + style: const TextStyle( + color: AppColors.coral, + fontSize: 20, + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const loc = 'https://github.com/AbdeltwabMF/Loc'; + await _launchUrl(loc); + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +Future _launchUrl(String url) async { + if (!await launchUrl(Uri.parse(url))) { + debugPrint('Can not launch $url'); + } +} diff --git a/lib/screens/home.dart b/lib/screens/home.dart index b39ead7..f8e3d0f 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:loc/screens/about.dart'; import 'package:loc/styles/colors.dart'; import 'package:loc/utils/location.dart'; import 'package:loc/utils/states.dart'; @@ -34,20 +35,20 @@ class HomeScreen extends StatelessWidget { ), margin: const EdgeInsets.all(8), child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Container( padding: const EdgeInsets.only( - top: 16, - right: 8, - left: 8, - bottom: 8, + top: 8, + right: 4, + left: 4, ), child: Text( - 'The True Location-based Reminder', + 'Loc, Have a nice day', textAlign: TextAlign.center, style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 20, + fontSize: 24, color: AppColors.fg.withOpacity(0.8), ), ), @@ -251,6 +252,172 @@ class HomeScreen extends StatelessWidget { ), ], ), + ], + ), + ), + const SizedBox( + height: 12, + ), + Container( + padding: const EdgeInsets.only( + left: 4, + right: 4, + ), + decoration: BoxDecoration( + border: Border.all( + color: AppColors.lavender, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.circular(8), + color: AppColors.lavender.withOpacity(0.2), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + bottom: 8, + ), + child: TextFormField( + controller: TextEditingController( + text: provider + .currDisplayNameController + .text), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.fg, + ), + ), + contentPadding: EdgeInsets.only( + top: 4, + bottom: 4, + left: 4, + right: 4, + ), + labelText: 'Current Location', + labelStyle: TextStyle( + fontFamily: 'Fantasque', + ), + prefixIcon: Icon( + Icons.my_location, + ), + ), + readOnly: true, + style: const TextStyle( + fontSize: 16, + fontFamily: 'NotoArabic', + ), + textAlign: TextAlign.right, + ), + ), + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.only( + top: 8, + left: 4, + right: 2, + bottom: 8, + ), + child: TextFormField( + controller: TextEditingController( + text: provider + .isCurrentLocationValid() + ? provider.currLatitude + ?.toString() + : null), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.ashGray, + width: 2, + strokeAlign: BorderSide + .strokeAlignOutside, + ), + ), + contentPadding: EdgeInsets.only( + top: 4, + bottom: 4, + left: 4, + right: 4, + ), + labelText: 'Latitude', + prefixIcon: Icon( + Icons.near_me, + ), + ), + readOnly: true, + ), + ), + ), + Expanded( + child: Container( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 4, + bottom: 8, + ), + child: TextFormField( + controller: TextEditingController( + text: provider + .isCurrentLocationValid() + ? provider.currLongitude + ?.toString() + : null), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.ashGray, + width: 2, + strokeAlign: BorderSide + .strokeAlignOutside, + ), + ), + contentPadding: EdgeInsets.only( + top: 4, + bottom: 4, + left: 4, + right: 4, + ), + labelText: 'Longitude', + prefixIcon: Icon( + Icons.near_me, + ), + ), + readOnly: true, + ), + ), + ), + ], + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + Container( + padding: const EdgeInsets.only( + left: 4, + right: 4, + ), + decoration: BoxDecoration( + border: Border.all( + color: AppColors.linen, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.circular(8), + color: AppColors.linen.withOpacity(0.2), + ), + child: Column( + children: [ Container( padding: const EdgeInsets.only( top: 8, @@ -313,233 +480,127 @@ class HomeScreen extends StatelessWidget { }, ), ), - ], - ), - ), - // here - const SizedBox( - height: 16, - ), - Container( - padding: const EdgeInsets.only( - left: 4, - right: 4, - ), - decoration: BoxDecoration( - border: Border.all( - color: AppColors.lavender, - strokeAlign: BorderSide.strokeAlignOutside, - ), - borderRadius: BorderRadius.circular(8), - color: AppColors.lavender.withOpacity(0.2), - ), - child: Column(children: [ - Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.only( - top: 8, - left: 2, - right: 2, - bottom: 8, - ), - child: TextFormField( - controller: - provider.distanceController, - decoration: const InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppColors.fg, - strokeAlign: BorderSide - .strokeAlignOutside, - ), - ), - contentPadding: EdgeInsets.only( - top: 4, - bottom: 4, - left: 4, - right: 4, - ), - labelText: 'Distance', - suffixText: 'Meters ', - suffixStyle: TextStyle( - fontFamily: 'Fantasque', - ), - prefixIcon: Icon( - Icons.straighten, - ), + Row( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + bottom: 8, ), - readOnly: true, - ), - ), - ), - Expanded( - child: Container( - padding: const EdgeInsets.only( - top: 8, - left: 2, - right: 2, - bottom: 8, - ), - child: TextFormField( - controller: - provider.bearingController, - decoration: const InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppColors.fg, - strokeAlign: BorderSide - .strokeAlignOutside, + child: TextFormField( + controller: + provider.distanceController, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.fg, + strokeAlign: BorderSide + .strokeAlignOutside, + ), ), - ), - contentPadding: EdgeInsets.only( - top: 4, - bottom: 4, - left: 4, - right: 4, - ), - labelText: 'Bearing', - suffixText: 'Degrees ', - suffixStyle: TextStyle( - fontFamily: 'Fantasque', - ), - prefixIcon: Icon( - Icons.straight, - ), - ), - readOnly: true, - ), - ), - ), - ], - ), - Container( - padding: const EdgeInsets.only( - top: 8, - left: 2, - right: 2, - bottom: 8, - ), - child: TextFormField( - controller: TextEditingController( - text: provider - .currDisplayNameController.text), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppColors.fg, - ), - ), - contentPadding: EdgeInsets.only( - top: 4, - bottom: 4, - left: 4, - right: 4, - ), - labelText: 'Current Location', - labelStyle: TextStyle( - fontFamily: 'Fantasque', - ), - prefixIcon: Icon( - Icons.my_location, - ), - ), - readOnly: true, - style: const TextStyle( - fontSize: 16, - fontFamily: 'NotoArabic', - ), - textAlign: TextAlign.right, - ), - ), - Row( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - padding: const EdgeInsets.only( - top: 8, - left: 4, - right: 2, - bottom: 8, - ), - child: TextFormField( - controller: TextEditingController( - text: provider - .isCurrentLocationValid() - ? provider.currLatitude - ?.toString() - : null), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppColors.ashGray, - width: 2, - strokeAlign: BorderSide - .strokeAlignOutside, + contentPadding: EdgeInsets.only( + top: 4, + bottom: 4, + left: 4, + right: 4, + ), + labelText: 'Distance', + suffixText: 'Meters ', + suffixStyle: TextStyle( + fontFamily: 'Fantasque', + ), + prefixIcon: Icon( + Icons.straighten, ), ), - contentPadding: EdgeInsets.only( - top: 4, - bottom: 4, - left: 4, - right: 4, - ), - labelText: 'Latitude', - prefixIcon: Icon( - Icons.near_me, - ), + readOnly: true, ), - readOnly: true, ), ), - ), - Expanded( - child: Container( - padding: const EdgeInsets.only( - top: 8, - left: 2, - right: 4, - bottom: 8, - ), - child: TextFormField( - controller: TextEditingController( - text: provider - .isCurrentLocationValid() - ? provider.currLongitude - ?.toString() - : null), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderSide: BorderSide( - color: AppColors.ashGray, - width: 2, - strokeAlign: BorderSide - .strokeAlignOutside, + Expanded( + child: Container( + padding: const EdgeInsets.only( + top: 8, + left: 2, + right: 2, + bottom: 8, + ), + child: TextFormField( + controller: + provider.bearingController, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + color: AppColors.fg, + strokeAlign: BorderSide + .strokeAlignOutside, + ), + ), + contentPadding: EdgeInsets.only( + top: 4, + bottom: 4, + left: 4, + right: 4, + ), + labelText: 'Bearing', + suffixText: 'Degrees ', + suffixStyle: TextStyle( + fontFamily: 'Fantasque', + ), + prefixIcon: Icon( + Icons.straight, ), ), - contentPadding: EdgeInsets.only( - top: 4, - bottom: 4, - left: 4, - right: 4, - ), - labelText: 'Longitude', - prefixIcon: Icon( - Icons.near_me, - ), + readOnly: true, ), - readOnly: true, ), ), + ], + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Divider( + color: AppColors.fg.withOpacity(0.5), + height: 4, + endIndent: 8, + indent: 8, + ), + ), + Container( + child: TextButton( + child: Row( + children: const [ + Icon( + Icons.info_rounded, + color: AppColors.fg, ), + Text( + ' About', + style: TextStyle( + color: AppColors.fg, + fontSize: 18, + ), + ) ], ), - ]), + onPressed: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return const About(); + }, + ), + ); + }, + ), ) ], ), diff --git a/lib/utils/location.dart b/lib/utils/location.dart index 9824430..08400a4 100644 --- a/lib/utils/location.dart +++ b/lib/utils/location.dart @@ -1,12 +1,9 @@ -import 'package:geolocator/geolocator.dart' as geo; -import 'package:geolocator_apple/geolocator_apple.dart'; -import 'package:geolocator_android/geolocator_android.dart'; import 'package:flutter/material.dart'; import 'package:flutter_ringtone_player/flutter_ringtone_player.dart'; +import 'package:geolocator/geolocator.dart' as geo; import 'package:loc/screens/osm_map_view.dart'; import 'package:loc/utils/states.dart'; import 'package:provider/provider.dart'; -import 'package:flutter/foundation.dart'; Future _checkPermissions() async { bool isServiceEnabled = await geo.Geolocator.isLocationServiceEnabled(); @@ -34,51 +31,29 @@ Future _checkPermissions() async { void handlePositionUpdates(BuildContext context) { final provider = Provider.of(context, listen: false); - late LocationSettings locationSettings; - if (defaultTargetPlatform == TargetPlatform.android) { - locationSettings = AndroidSettings( - accuracy: LocationAccuracy.high, - distanceFilter: 50, - forceLocationManager: true, - intervalDuration: const Duration(seconds: 10), - ); - } else if (defaultTargetPlatform == TargetPlatform.iOS || - defaultTargetPlatform == TargetPlatform.macOS) { - locationSettings = AppleSettings( - accuracy: LocationAccuracy.best, - activityType: ActivityType.fitness, - distanceFilter: 16, - pauseLocationUpdatesAutomatically: true, - showBackgroundLocationIndicator: false, - ); - } else { - locationSettings = const LocationSettings( - accuracy: LocationAccuracy.best, - distanceFilter: 16, - ); - } - - final subscription = - geo.Geolocator.getPositionStream(locationSettings: locationSettings) - .listen( - (value) async { - provider.setCurrLatitude(value.latitude); - provider.setCurrLongitude(value.longitude); - - provider.setDistance(distanceAndBearing(context)?.first); - provider.setBearing(distanceAndBearing(context)?.last); - shouldPlaySound(context); - - LocationData locationData = LocationData( - latitude: value.latitude, longitude: value.longitude); - final decodedResponse = await osmReverse(locationData); - provider.setCurrDisplayName(decodedResponse['display_name']); - }, - cancelOnError: true, - onError: (error, stackTrace) { - debugPrint(error.toString()); - debugPrint(stackTrace.toString()); - }); + final subscription = geo.Geolocator.getPositionStream( + distanceFilter: 16, + forceAndroidLocationManager: true, + intervalDuration: const Duration(seconds: 10), + ).listen( + (value) async { + provider.setCurrLatitude(value.latitude); + provider.setCurrLongitude(value.longitude); + + provider.setDistance(distanceAndBearing(context)?.first); + provider.setBearing(distanceAndBearing(context)?.last); + shouldPlaySound(context); + + LocationData locationData = + LocationData(latitude: value.latitude, longitude: value.longitude); + final decodedResponse = await osmReverse(locationData); + provider.setCurrDisplayName(decodedResponse['display_name']); + }, + cancelOnError: true, + onError: (error, stackTrace) { + debugPrint(error.toString()); + debugPrint(stackTrace.toString()); + }); provider.setPositionStream(subscription); } diff --git a/lib/widgets/buttons.dart b/lib/widgets/buttons.dart index 90d6c53..f2ba16b 100644 --- a/lib/widgets/buttons.dart +++ b/lib/widgets/buttons.dart @@ -15,7 +15,7 @@ class PickLocationButton extends StatelessWidget { top: 8, left: 4, right: 4, - bottom: 8, + bottom: 0, ), child: ElevatedButton( style: ButtonStyle( @@ -23,21 +23,36 @@ class PickLocationButton extends StatelessWidget { elevation: MaterialStateProperty.all(0), padding: MaterialStateProperty.all( const EdgeInsets.symmetric(vertical: 12)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), side: MaterialStateProperty.all( const BorderSide( - color: AppColors.lavender, + color: AppColors.blue, width: 1, strokeAlign: BorderSide.strokeAlignOutside, ), ), ), onPressed: onPressed, - child: Text( - 'Set destination', - style: TextStyle( - color: AppColors.fg.withOpacity(0.9), - fontSize: 20, - fontWeight: FontWeight.bold), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: const [ + Icon( + Icons.location_searching, + color: AppColors.fg, + ), + Text( + ' Select Destination', + style: TextStyle( + color: AppColors.fg, + fontSize: 20, + ), + ), + ], ), ), ); @@ -66,19 +81,31 @@ class OpenMapButton extends StatelessWidget { backgroundColor: MaterialStateProperty.all( isListening ? AppColors.blue : AppColors.babyBlue, ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), elevation: MaterialStateProperty.all(0), padding: MaterialStateProperty.all( const EdgeInsets.symmetric(vertical: 12)), ), - child: Text( - 'Open Map', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: isListening - ? AppColors.fg.withOpacity(0.5) - : AppColors.fg.withOpacity(0.8), - ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon( + Icons.map_rounded, + color: AppColors.fg, + ), + Text( + ' Open Map', + style: TextStyle( + fontSize: 20, + color: AppColors.fg, + ), + ), + ], ), ), ); @@ -115,25 +142,39 @@ class StartButton extends StatelessWidget { : AppColors.linen : AppColors.ashGray, ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), elevation: MaterialStateProperty.all(0), padding: MaterialStateProperty.all( const EdgeInsets.symmetric(vertical: 12)), ), - child: Text( - isListening == true - ? isLocationValid == true - ? 'Stop' - : 'Calculating...' - : 'Start', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: isListening == true - ? isLocationValid == true - ? AppColors.rose - : AppColors.fg - : AppColors.fg.withOpacity(0.8), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + isListening == true + ? isLocationValid == true + ? Icons.stop_circle_rounded + : Icons.calculate_rounded + : Icons.navigation_rounded, + color: AppColors.fg, + ), + Text( + isListening == true + ? isLocationValid == true + ? ' Stop' + : ' Calculating' + : ' Start', + style: const TextStyle( + fontSize: 20, + color: AppColors.fg, + ), + ), + ], ), ), ); diff --git a/pubspec.yaml b/pubspec.yaml index 680d1e0..566f606 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: Free and open-spurce location-based reminder for Android publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 0.2.0 +version: 0.2.2 environment: sdk: '>=2.18.6 <3.0.0' @@ -18,6 +18,14 @@ dependencies: http: ^0.13.5 latlong2: ^0.8.1 provider: ^6.0.5 + url_launcher: ^6.1.8 + +dependency_overrides: + geolocator: + git: + url: https://gitlab.com/free2pass/flutter-geolocator-floss.git + ref: master + path: geolocator dev_dependencies: flutter_test: @@ -28,6 +36,7 @@ dev_dependencies: flutter: assets: - assets/ca/ + - assets/images/app_icon.png fonts: - family: Fantasque fonts: