summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRishi-k-s <rishikrishna.sr@gmail.com>2025-09-20 14:41:51 +0530
committerRishi-k-s <rishikrishna.sr@gmail.com>2025-09-20 14:41:51 +0530
commit74eb9c806279bd1bb284fb3021733031752af9ae (patch)
tree17ce949d82de6f44556b44b631bf0fa0ce6533b0
parent2979cf18e1f9036ee092859d224e410026d9b4f5 (diff)
readme
-rw-r--r--README.md139
-rw-r--r--lib/main.dart66
-rw-r--r--lib/models/place_prediction.dart12
-rw-r--r--lib/services/location_cache_service.dart64
-rw-r--r--macos/Flutter/GeneratedPluginRegistrant.swift2
-rw-r--r--pubspec.lock104
-rw-r--r--pubspec.yaml2
7 files changed, 363 insertions, 26 deletions
diff --git a/README.md b/README.md
index 042f411..fa60366 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,137 @@
-# engotta_app
+# Engotta (എങ്ങോട്ടാ) - Smart Navigation Companion
-A new Flutter project.
+![License](https://img.shields.io/github/license/Rishi-k-s/engotta_app)
+![Flutter Version](https://img.shields.io/badge/flutter-^3.8.1-blue)
+
+## What is Engotta?
+
+"എങ്ങോട്ടാ" (Engotta) - Malayalam for "Where to?" - is an innovative open hardware navigation solution designed specifically for two-wheeler riders. Born out of the frustration of constantly stopping to check maps and missing turns, Engotta provides a seamless navigation experience through a handlebar-mounted display.
+
+## Features
+
+### Companion App
+- 🎯 Real-time location tracking
+- 🗺️ Intelligent route planning
+- 📍 Location search with smart suggestions
+- 💾 Recent locations caching
+- 📱 User-friendly interface
+- 🔄 Current location detection
+- 🏃 Performance optimized with debouncing
+
+### Hardware Component (Coming Soon)
+- 📺 Handlebar-mounted display
+- 🧭 Turn-by-turn navigation
+- 🛠️ Custom PCB design
+- 🖨️ 3D printed mounting case
+- 🔋 Weather-resistant design
+- 📡 Bluetooth connectivity
+
+## App Screenshots
+[Coming Soon]
## Getting Started
-This project is a starting point for a Flutter application.
+### Prerequisites
+- Flutter SDK ^3.8.1
+- Android Studio / VS Code
+- Google Maps API Key
+
+### Installation
+
+1. Clone the repository
+```bash
+git clone https://github.com/Rishi-k-s/engotta_app.git
+```
+
+2. Navigate to project directory
+```bash
+cd engotta_app
+```
+
+3. Install dependencies
+```bash
+flutter pub get
+```
+
+4. Add your Google Maps API key
+ - Create `lib/config/api_keys.dart`
+ - Add your API key:
+```dart
+class ApiKeys {
+ static const String googlePlacesApi = 'YOUR_API_KEY';
+}
+```
+
+5. Run the app
+```bash
+flutter run
+```
+
+## Hardware Component
+
+The hardware component is currently under development. It will include:
+- Custom PCB design files
+- 3D printable case models
+- Assembly instructions
+- Component list
+- Wiring diagrams
+
+[Coming Soon]
+
+## Contributing
+
+We welcome contributions! Whether it's:
+- 🐛 Bug fixes
+- ✨ New features
+- 📚 Documentation improvements
+- 🎨 UI/UX enhancements
+
+Please read our [Contributing Guidelines](CONTRIBUTING.md) before making a pull request.
+
+## Roadmap
+
+- [x] Initial app development
+- [x] Location services integration
+- [x] Search functionality
+- [x] Location caching
+- [ ] Hardware prototype
+- [ ] Custom PCB design
+- [ ] 3D printed case
+- [ ] Bluetooth connectivity
+- [ ] Turn-by-turn navigation
+- [ ] Weather resistance testing
+
+## Tech Stack
+
+### App
+- Flutter
+- Google Maps API
+- Geolocator
+- SharedPreferences
+- HTTP
+
+### Hardware (Planned)
+- Custom PCB
+- ESP32
+- LED Display
+- 3D Printed Components
+
+## About the Project
+
+Engotta was born from a simple yet common problem - the need to check maps repeatedly while riding. As a rider myself, I found it frustrating and potentially dangerous to keep stopping to check directions. This project aims to solve this problem with a simple, effective, and affordable solution.
+
+The name "എങ്ങോട്ടാ" (Engotta) comes from Malayalam, meaning "Where to?" - a common question we ask fellow riders. It perfectly encapsulates the purpose of this project - helping riders reach their destination safely and efficiently.
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## Contact
+
+Rishi K S - [@YourTwitter](https://twitter.com/YourTwitter)
-A few resources to get you started if this is your first Flutter project:
+Project Link: [https://github.com/Rishi-k-s/engotta_app](https://github.com/Rishi-k-s/engotta_app)
-- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+---
-For help getting started with Flutter development, view the
-[online documentation](https://docs.flutter.dev/), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
+<p align="center">Made with ❤️ for the riding community</p>
diff --git a/lib/main.dart b/lib/main.dart
index 4c8d838..eb14d61 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'services/places_service.dart';
import 'services/location_service.dart';
+import 'services/location_cache_service.dart';
import 'models/place_prediction.dart';
import 'utils/debouncer.dart';
@@ -41,13 +42,36 @@ class _MapSampleState extends State<MapSample> {
final PlacesService _placesService = PlacesService();
final LocationService _locationService = LocationService();
final Debouncer _searchDebouncer = Debouncer();
+ late LocationCacheService _locationCacheService;
Future<Iterable<PlacePrediction>> _getLocationSuggestions(String query) async {
- // Return empty list if query is empty or less than 3 characters
- if (query.isEmpty || query.length < 3) {
+ final normalizedQuery = query.toLowerCase();
+
+ // Handle empty query - show current location and recent locations
+ if (query.isEmpty) {
+ final recentLocations = await _locationCacheService.getCachedLocations();
+ return [
+ PlacePrediction.currentLocation(),
+ ...recentLocations,
+ ];
+ }
+
+ // Show current location if query matches
+ if ('current location'.contains(normalizedQuery)) {
+ return [PlacePrediction.currentLocation()];
+ }
+
+ // For other queries, require minimum 3 characters
+ if (query.length < 3) {
return const Iterable<PlacePrediction>.empty();
}
+ // Check if query matches any recent locations
+ final recentLocations = await _locationCacheService.getCachedLocations();
+ final matchingRecent = recentLocations.where((location) =>
+ location.mainText.toLowerCase().contains(normalizedQuery) ||
+ location.secondaryText.toLowerCase().contains(normalizedQuery));
+
Completer<Iterable<PlacePrediction>> completer = Completer();
_searchDebouncer.call(() async {
@@ -58,12 +82,22 @@ class _MapSampleState extends State<MapSample> {
longitude: _center.longitude,
);
if (!completer.isCompleted) {
- completer.complete(predictions);
+ final results = [
+ if ('current location'.contains(normalizedQuery))
+ PlacePrediction.currentLocation(),
+ ...matchingRecent,
+ ...predictions.where((p) => !matchingRecent.any((r) => r.placeId == p.placeId))
+ ];
+ completer.complete(results);
}
} catch (e) {
print('Error getting predictions: $e');
if (!completer.isCompleted) {
- completer.complete(const Iterable<PlacePrediction>.empty());
+ if (matchingRecent.isNotEmpty) {
+ completer.complete(matchingRecent);
+ } else {
+ completer.complete(const Iterable<PlacePrediction>.empty());
+ }
}
}
});
@@ -75,7 +109,12 @@ class _MapSampleState extends State<MapSample> {
void initState() {
super.initState();
_center = LocationService.defaultLocation;
- _initializeLocation();
+ _initializeServices();
+ }
+
+ Future<void> _initializeServices() async {
+ _locationCacheService = await LocationCacheService.create();
+ await _initializeLocation();
}
Future<void> _initializeLocation() async {
@@ -110,9 +149,20 @@ class _MapSampleState extends State<MapSample> {
optionsBuilder: (TextEditingValue textEditingValue) async {
return await _getLocationSuggestions(textEditingValue.text);
},
- onSelected: (PlacePrediction selection) {
- _fromController.text = selection.mainText;
- // Here you would typically update the map position
+ onSelected: (PlacePrediction selection) async {
+ if (selection.isCurrentLocation) {
+ final currentLocation = await _locationService.getCurrentLocation();
+ setState(() {
+ _center = currentLocation;
+ });
+ mapController.animateCamera(CameraUpdate.newLatLng(_center));
+ _fromController.text = 'Current Location';
+ } else {
+ _fromController.text = selection.mainText;
+ // Cache the selected location
+ await _locationCacheService.addToCache(selection);
+ // Here you would typically update the map position
+ }
},
displayStringForOption: (PlacePrediction option) =>
'${option.mainText}, ${option.secondaryText}',
diff --git a/lib/models/place_prediction.dart b/lib/models/place_prediction.dart
index 95fc5c6..2d019d7 100644
--- a/lib/models/place_prediction.dart
+++ b/lib/models/place_prediction.dart
@@ -3,14 +3,26 @@ class PlacePrediction {
final String mainText;
final String secondaryText;
final List<String> types;
+ final bool isCurrentLocation;
PlacePrediction({
required this.placeId,
required this.mainText,
required this.secondaryText,
required this.types,
+ this.isCurrentLocation = false,
});
+ factory PlacePrediction.currentLocation() {
+ return PlacePrediction(
+ placeId: 'current_location',
+ mainText: '📍 Current Location',
+ secondaryText: 'Use my current location',
+ types: ['current_location'],
+ isCurrentLocation: true,
+ );
+ }
+
factory PlacePrediction.fromJson(Map<String, dynamic> json) {
final prediction = json['placePrediction'];
return PlacePrediction(
diff --git a/lib/services/location_cache_service.dart b/lib/services/location_cache_service.dart
new file mode 100644
index 0000000..e914fda
--- /dev/null
+++ b/lib/services/location_cache_service.dart
@@ -0,0 +1,64 @@
+import 'dart:convert';
+import 'package:shared_preferences/shared_preferences.dart';
+import '../models/place_prediction.dart';
+
+class LocationCacheService {
+ static const String _cacheKey = 'recent_locations';
+ static const int _maxCacheSize = 5;
+ final SharedPreferences _prefs;
+
+ LocationCacheService(this._prefs);
+
+ static Future<LocationCacheService> create() async {
+ final prefs = await SharedPreferences.getInstance();
+ return LocationCacheService(prefs);
+ }
+
+ Future<List<PlacePrediction>> getCachedLocations() async {
+ final jsonList = _prefs.getStringList(_cacheKey) ?? [];
+ return jsonList.map((jsonStr) {
+ final map = json.decode(jsonStr);
+ return PlacePrediction(
+ placeId: map['placeId'],
+ mainText: map['mainText'],
+ secondaryText: map['secondaryText'],
+ types: List<String>.from(map['types']),
+ );
+ }).toList();
+ }
+
+ Future<void> addToCache(PlacePrediction location) async {
+ if (location.isCurrentLocation) return; // Don't cache current location option
+
+ final jsonList = _prefs.getStringList(_cacheKey) ?? [];
+ final locations = jsonList.map((jsonStr) => json.decode(jsonStr)).toList();
+
+ // Remove if location already exists (to move it to top)
+ locations.removeWhere((loc) => loc['placeId'] == location.placeId);
+
+ // Add new location at the beginning
+ locations.insert(0, {
+ 'placeId': location.placeId,
+ 'mainText': location.mainText,
+ 'secondaryText': location.secondaryText,
+ 'types': location.types,
+ });
+
+ // Keep only the most recent locations
+ if (locations.length > _maxCacheSize) {
+ locations.removeRange(_maxCacheSize, locations.length);
+ }
+
+
+
+ // Save back to preferences
+ await _prefs.setStringList(
+ _cacheKey,
+ locations.map((loc) => json.encode(loc)).toList(),
+ );
+ }
+
+ Future<void> clearCache() async {
+ await _prefs.remove(_cacheKey);
+ }
+} \ No newline at end of file
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index d7ee0bc..8679ef3 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -7,8 +7,10 @@ import Foundation
import geolocator_apple
import package_info_plus
+import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
+ SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
}
diff --git a/pubspec.lock b/pubspec.lock
index b928664..2df882c 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -81,14 +81,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.11"
- dropdown_search:
- dependency: "direct main"
- description:
- name: dropdown_search
- sha256: "55106e8290acaa97ed15bea1fdad82c3cf0c248dd410e651f5a8ac6870f783ab"
- url: "https://pub.dev"
- source: hosted
- version: "5.0.6"
fake_async:
dependency: transitive
description:
@@ -105,6 +97,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
fixnum:
dependency: transitive
description:
@@ -368,6 +368,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
petitparser:
dependency: transitive
description:
@@ -376,6 +400,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@@ -392,6 +424,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.3"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.12"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.4"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
diff --git a/pubspec.yaml b/pubspec.yaml
index 36fbe0f..78b178d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -36,8 +36,8 @@ dependencies:
cupertino_icons: ^1.0.8
google_maps_flutter: ^2.13.1
http: ^1.5.0
- dropdown_search: ^5.0.6
geolocator: ^14.0.2
+ shared_preferences: ^2.5.3
dev_dependencies:
flutter_test: