Skip to content

Commit

Permalink
Merge commit '0b339ff0dd2a2bdf55e5148161215a7008748b7c' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
sh4dowByte committed Jan 15, 2025
2 parents 5cac03b + 0b339ff commit e41c123
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ jobs:
flutter-version: '3.19.3'
channel: 'stable'

- name: Inject API Key into AndroidManifest.xml
- name: Inject API Key
run: |
sed -i '' 's/YOUR_MAPS_API_KEY/${{ secrets.MAPS_API_KEY }}/' android/app/src/main/AndroidManifest.xml
sed -i '' 's/YOUR_MAPS_API_KEY/${{ secrets.MAPS_API_KEY }}/' lib/services/google_map_service.dart
#4 Install Dependencies
- name: Install Dependencies
Expand Down
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
<img src="https://badgen.net/badge/Flutter/3.19.3/blue" alt="Flutter Version" style="max-width: 100%;">
<img src="https://badgen.net/badge/Dart/3.3.1/blue" alt="Dart Version" style="max-width: 100%;">
<img src="https://img.shields.io/badge/License-CC%20BY--NC%204.0-lightgrey.svg" alt="Dart Version" style="max-width: 100%;">

</p>

</p>

# Flutter Taksi UI

**Flutter Taksi UI** is a modern and responsive mobile application developed using Flutter, designed to provide an intuitive and efficient taxi booking experience. This app is based on slicing from a Figma design, optimized for mobile screens. With a clean and user-friendly interface, it allows users to easily book, track, and manage taxi rides.

The application features modern design elements, including interactive maps, smooth transition animations, and custom icons for each vehicle type. Key functionalities such as selecting pick-up/destination locations, fare estimation, and a list of available vehicles are designed to ensure a seamless user experience. Powered by Flutter, the app guarantees fast and responsive performance across various devices, making it an ideal solution for developers aiming to create aesthetically pleasing and high-performance taxi booking applications.

<a href="https://github.com/sh4dowByte/flutter_ui_taksi/releases/download/v1.0.0%2B1-1/app-release.apk">
<a href="https://github.com/sh4dowByte/flutter_ui_taksi/releases/download/v1.0.0%2B1-4/app-release.apk">
<img src="https://playerzon.com/asset/download.png" width="200" data-canonical-src="https://playerzon.com/asset/download.png" style="max-width: 100%;">
</a>

Expand All @@ -27,11 +26,10 @@ The design of this application is based on the provided Figma file. The slicing

The application features:

- **Google Maps Integration**: Interactive maps for selecting pick-up and destination locations with real-time tracking.
- **Google Maps Integration**: Interactive maps for selecting pick-up.
- **Responsive Design**: Supports various screen sizes with responsive UI elements.
- **Seamless Navigation**: Implements navigation using Flutter Navigator for a smooth user flow.
- **Reusable Components**: Modular components for easier development and maintenance.
- **Modern Design Elements**: Custom icons for vehicle types, smooth transition animations, and aesthetically pleasing visuals.

## 🛠️ Technologies

Expand All @@ -55,13 +53,41 @@ This project is built using the following technologies:
└── pubspec.yaml # Flutter project configuration
```

## ⚙️ Setup Instructions

To run this application successfully, please follow the setup steps below:

### 1. Update API Keys in `AndroidManifest.xml`

Before running the application, you need to replace the placeholder for your API key in the `AndroidManifest.xml` file.

1. Open `android/app/src/main/AndroidManifest.xml`.
2. Locate the line with the placeholder `YOUR_MAPS_API_KEY` in the `<meta-data>` tag.
3. Replace `YOUR_MAPS_API_KEY` with your actual API key.

Example:

```xml
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_ACTUAL_API_KEY" />
```

### 2. Update API Keys in `google_map_service.dart`

Before running the application, you need to replace the placeholder for your API key in the `google_map_service.dart` file.

1. Open ` lib/services/google_map_service.dart`.
2. Replace `YOUR_MAPS_API_KEY` with your actual API key.


## 🖼️ Screenshot

| ![1733104646007](image/README/1733104646007.png) | |
| --------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| ![1733104646007](image/README/1733104646007.png) | |
| ---------------------------------------------- | ---------------------------------------------- |
| ![1733104679796](image/README/1733104679796.png) | ![1733104729341](image/README/1733104729341.png) |
| ![1733104764204](image/README/1733104764204.png) | ![1733104803940](image/README/1733104803940.png) |
| ![1733104849994](image/README/1733104849994.png) | |
| ![1733104764204](image/README/1733104764204.png) | ![1733104803940](image/README/1733104803940.png) |
| ![1733104849994](image/README/1733104849994.png) | |

## License

Expand Down
Binary file added assets/marker/car-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
206 changes: 202 additions & 4 deletions lib/screens/pick_up/components/map_component.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_ui_taksi/config/pallete.dart';
import 'package:flutter_ui_taksi/services/google_map_service.dart';
import 'package:geocoding/geocoding.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

Expand Down Expand Up @@ -26,7 +30,53 @@ class _MapComponentState extends State<MapComponent> {
Set<Marker> _markers = {};
String? _mapStyle;
bool myLocationInit = false;
double iconSize = 100;
double iconSize = 70;
double _mapRotation = 0.0;

// Tambahkan variabel polyline
Set<Polyline> _polylines = {};

Future<List<LatLng>> drawRoute(LatLng origin, LatLng destination) async {
final routeCoordinates = await getRoute(origin, destination);

setState(() {
_polylines.clear();

_polylines.add(
Polyline(
polylineId: const PolylineId('route'),
color: Pallete.color5, // Warna rute
width: 3, // Ketebalan garis
points: routeCoordinates, // Koordinat jalur
),
);
});

// Pindahkan kamera agar mencakup jalur
// mapController.animateCamera(
// CameraUpdate.newLatLngBounds(
// LatLngBounds(
// southwest: routeCoordinates.first,
// northeast: routeCoordinates.last,
// ),
// 50, // Padding
// ),
// );
// ignore: empty_catches
return routeCoordinates;
}

void _updateMarkerRotation(double newRotation) {
setState(() {
_markers = _markers.map((marker) {
if (marker.markerId == const MarkerId('riderMarker')) {
// Sesuaikan rotasi marker
return marker.copyWith(rotationParam: 360 - newRotation + 130);
}
return marker;
}).toSet();
});
}

@override
void initState() {
Expand Down Expand Up @@ -54,6 +104,7 @@ class _MapComponentState extends State<MapComponent> {
myLocationInit = true;
_markers.add(
Marker(
anchor: const Offset(0.5, 0.5),
markerId: const MarkerId('myLocationMarker'),
position: _initialMarkerPosition,
icon: customIcon,
Expand All @@ -80,6 +131,8 @@ class _MapComponentState extends State<MapComponent> {
Marker(
markerId: const MarkerId('destinationMarker'),
position: _initialMarkerPosition,
anchor: const Offset(0.5, 0.5),

icon: customIcon,
draggable: true, // Aktifkan dragging
onDragEnd: (newPositions) {
Expand Down Expand Up @@ -118,8 +171,8 @@ class _MapComponentState extends State<MapComponent> {

void addRiderMarker() async {
final BitmapDescriptor customIcon = await getResizedIcon(
'assets/marker/car-icon-splash.png', // Path gambar ikon
150, // Ukuran ikon
'assets/marker/car-icon.png', // Path gambar ikon
80, // Ukuran ikon
);

LatLng position = const LatLng(-2.999283, 114.388786);
Expand All @@ -130,11 +183,134 @@ class _MapComponentState extends State<MapComponent> {
markerId: const MarkerId('riderMarker'),
position: position,
icon: customIcon,
rotation: 130),
anchor: const Offset(0.5, 0.5),
rotation: 280),
);
});

_moveCameraWithOffset(position, zoom: 15, offset: -0.007);

if (myLocation != null) {
final route = await drawRoute(position, myLocation!);
moveMarkerAlongRoute(
route, const MarkerId('riderMarker')); // Gerakkan marker
}
}

Future<void> moveMarkerAlongRoute(
List<LatLng> route, MarkerId markerId) async {
const int duration = 120; // Durasi total animasi dalam detik (2 menit)
if (route.isEmpty || route.length < 2) return;

double totalDistance = 0.0;
for (int i = 0; i < route.length - 1; i++) {
totalDistance += _calculateDistance(route[i], route[i + 1]);
}

for (int i = 0; i < route.length - 1; i++) {
LatLng start = route[i];
LatLng end = route[i + 1];

double segmentDistance = _calculateDistance(start, end);
int segmentDuration =
((segmentDistance / totalDistance) * duration * 1000).toInt();
final int steps = segmentDuration ~/ 16; // 16ms per frame (60 FPS)

// Hitung rotasi berdasarkan arah
double rotation = _calculateBearing(start, end);

for (int step = 0; step < steps; step++) {
final double t = step / steps;
final LatLng interpolatedPosition = LatLng(
start.latitude + (end.latitude - start.latitude) * t,
start.longitude + (end.longitude - start.longitude) * t,
);

// Update posisi marker dan polyline
setState(() {
_markers = _markers.map((marker) {
if (marker.markerId == markerId) {
return marker.copyWith(
positionParam: interpolatedPosition,
rotationParam: rotation,
);
}
return marker;
}).toSet();

// Perbarui polyline
_polylines = _polylines.map((polyline) {
if (polyline.polylineId.value == 'route') {
// Perbarui rute dengan sisa jalur
final remainingRoute = route.sublist(i);
return polyline.copyWith(pointsParam: remainingRoute);
}
return polyline;
}).toSet();
});

await Future.delayed(const Duration(milliseconds: 16));
}
}

// Pastikan marker berada di titik akhir
setState(() {
_markers = _markers.map((marker) {
if (marker.markerId == markerId) {
return marker.copyWith(
positionParam: route.last,
rotationParam:
_calculateBearing(route[route.length - 2], route.last),
);
}
return marker;
}).toSet();

// Hapus rute (opsional)
_polylines = _polylines.map((polyline) {
if (polyline.polylineId.value == 'route') {
return polyline.copyWith(pointsParam: []);
}
return polyline;
}).toSet();
});
}

double _calculateBearing(LatLng start, LatLng end) {
final double startLat = _degreesToRadians(start.latitude);
final double startLng = _degreesToRadians(start.longitude);
final double endLat = _degreesToRadians(end.latitude);
final double endLng = _degreesToRadians(end.longitude);

final double dLng = endLng - startLng;
final double y = sin(dLng) * cos(endLat);
final double x =
cos(startLat) * sin(endLat) - sin(startLat) * cos(endLat) * cos(dLng);
final double bearing = atan2(y, x);

return (_radiansToDegrees(bearing) + 360) % 360;
}

double _radiansToDegrees(double radians) {
return radians * 50 / pi;
}

double _calculateDistance(LatLng start, LatLng end) {
const double earthRadius = 6371; // Radius bumi dalam kilometer
final double dLat = _degreesToRadians(end.latitude - start.latitude);
final double dLng = _degreesToRadians(end.longitude - start.longitude);

final double a = (sin(dLat / 2) * sin(dLat / 2)) +
cos(_degreesToRadians(start.latitude)) *
cos(_degreesToRadians(end.latitude)) *
(sin(dLng / 2) * sin(dLng / 2));
final double c = 2 * atan2(sqrt(a), sqrt(1 - a));

return earthRadius * c;
}

double _degreesToRadians(double degrees) {
return degrees * pi / 50;
}

void changeMarkerPosition(LatLng newPosition) async {
Expand All @@ -152,6 +328,7 @@ class _MapComponentState extends State<MapComponent> {
final updatedMarker = Marker(
markerId: const MarkerId('myLocationMarker'),
position: newPosition,
anchor: const Offset(0.5, 0.5),
icon: customIcon, // Gunakan ikon yang sama
draggable: true, // Pastikan draggable tetap aktif jika dibutuhkan
onDragEnd: (newPositions) {
Expand Down Expand Up @@ -205,6 +382,9 @@ class _MapComponentState extends State<MapComponent> {
);
}

late final LatLng? myLocation;
late final LatLng? myDestination;

void pinMarker(String markerId) async {
final BitmapDescriptor customIcon = await getResizedIcon(
markerId == 'myLocationMarker'
Expand All @@ -223,13 +403,24 @@ class _MapComponentState extends State<MapComponent> {
markerId: MarkerId(markerId),
icon: customIcon,
draggable: false,
anchor: const Offset(0.5, 0.5),
position: position);

_markers.removeWhere((marker) => marker.markerId == MarkerId(markerId));
_markers.add(updatedMarker);

if (markerId == 'myLocationMarker') {
myLocation = position;
} else {
myDestination = position;
}

_moveCameraWithOffset(position);
});

if (myLocation != null && myDestination != null) {
drawRoute(myLocation!, myDestination!);
}
}

@override
Expand All @@ -243,6 +434,13 @@ class _MapComponentState extends State<MapComponent> {
),
style: _mapStyle,
markers: _markers,
polylines: _polylines,
onCameraMove: (CameraPosition position) {
setState(() {
_mapRotation = position.bearing; // Simpan rotasi
_updateMarkerRotation(_mapRotation); // Perbarui marker
});
},
),
);
}
Expand Down
Loading

0 comments on commit e41c123

Please sign in to comment.