diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0293d1 --- /dev/null +++ b/README.md @@ -0,0 +1,307 @@ + +# Binar Final Project - Skyfly Android + +
+ Design Spread +
+
+ + +### Team C-1 Skyfly! + +Skyfly API allows you to get the needed resources to make Skyfly application run seamlessly. Some of this service is using authentication to access each service. You need to login first to access the service. + +### Overview + +SkyFly is an online flight ticket booking application designed to provide a seamless and user-friendly experience for booking flights. This Android application supports various functionalities essential for an efficient flight booking system. + +# Data Team C1 + +| | | **LinkedIn** | **Github** | +| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------- | +| **FSW** | _Reinanda Faris_ | [LinkedIn](https://www.linkedin.com/in/reinanda-faris/) | [Github](https://github.com/Reinandafaris) | +| | _Viery Nugroho_ | [LinkedIn](https://www.linkedin.com/in/viery-nugroho) | [Github](https://github.com/vierynugroho) | +| | _Andhika Rizky Aulia_ | [LinkedIn](https://www.linkedin.com/in/andhika-rizky/) | [Github](https://github.com/ndikrp) | +| | _Ananda Ias Falah_ | [LinkedIn](https://www.linkedin.com/in/falahsuryagemilang/) | [Github](https://github.com/falahsg) | +| | _Naufal Ady Saputro_ | [LinkedIn](https://www.linkedin.com/in/naufal-ady-saputro-71050b24b/) | [Github](https://github.com/naufaladysaputro) | +| | _Rizki Mauludin Yoga P._ | [LinkedIn](https://www.linkedin.com/in/riski-mauludin-yoga-8718602b0/) | [Github](https://github.com/RMYP) | +| | _Rafi Husein Bagaskara_ | [LinkedIn](https://www.linkedin.com/in/rafi-husein-257a76291) | [Github](https://github.com/raisenbk) | +| | _Lowis Armando Hutabarat_ | [LinkedIn](www.linkedin.com/in/lowis-armando-hutabarat-80b7612b3) | [Github](https://github.com/LowisHutabarat) | +| | | +| **AND** | _Komang Yuda Saputra_ | [LinkedIn](https://www.linkedin.com/in/komang-yuda-saputra-abb21b291/) | [Github](https://github.com/YudaSaputraa) | +| | _Ihsan Widagdo_ | [LinkedIn](https://www.linkedin.com/in/ihsan-widagdo/) | [Github](https://github.com/dagdo03) | +| | _Bella Febriany Nawangsari_ | [LinkedIn](https://www.linkedin.com/in/bella-febriany-nawangsari-4642a3291/) | [Github](https://github.com/bellafebrianyn) | +| | _Mochammad Yusuf Pratama_ | | | +| | | +| **Project Title** | _SKY-FLY_ | +| | | +| **Note** | _Binar KM6_ | +| | [Trello Team C1 Binar KM6](https://trello.com/c/2XzOhXim/60-c1-binar-km6-fsw-x-and) | +| | [Daily Stand-Up Team C1 Binar KM6](https://docs.google.com/spreadsheets/d/1aCpje7mQnG5uhmBOh9sEThQKYgatLdNpPoSoQK6VUvk/edit#gid=1785037003) | +| | [Deployed API](https://backend-skyfly-c1.vercel.app/api-docs/) | + +--- + + + + +## Tech Stack + +- MVVM (Model View ViewModel) +- Repository Pattern +- Coroutine Flow +- Koin Dependency Injection +- Coil Image Loader +- Retrofit +- OkHttp +- Groupie +- Room +- Chucker +- JUnit 4 +- Firebase +- Postman + +## How to Use the App + +### Initial Setup +- **Onboarding Screen**: The first time you install the app, an onboarding screen will be displayed. +- **Home Tab**: After completing the onboarding, you'll be navigated to the Home tab. + +### Home Tab +- **Selecting Flight Details**: + - Choose the origin and destination. + - Select the departure date. + - Specify the number of passengers and seat class. +- **Viewing Available Flights**: + - Once you fill out the form, you'll be directed to the available flights page. + - Select a flight to view its details. +- **Flight Detail**: + - After selecting a flight, you will be taken to the flight detail screen. +- **Authentication**: + - If you're not logged in, you'll be prompted to log in or register. + - You can create a new account or log in if you already have one. +- **Ticket and Passenger Form**: + - Fill out the ticket booking form and the passenger details form. +- **Seat Selection**: + - Navigate to the seat view to choose your desired seat. +- **Flight Detail Review**: + - Review the flight details you've selected. +- **Payment**: + - Proceed to the payment menu, where various payment methods are available, such as transfer and QRIS. +- **Ticket Confirmation**: + - After completing the payment, you'll see the detailed ticket information. + +### History Tab +- **Viewing Ticket History**: + - All your ticket booking history will be displayed here. + - Click on any booking to view its details. +- **Transaction Status**: + - The tab shows the status of your transactions: "PAID", "UNPAID", or "CANCELED". + - If you have unpaid bookings and want to cancel them, click the "cancel transaction" button below the details. + +### Notification Tab +- **Viewing Notifications**: + - View all notifications related to your account. + - Click on a notification to see more details. + +### Account Tab +- **Viewing and Editing Profile**: + - View your personal information and edit details such as your name and phone number. +- **Changing Password**: + - Click the button to change your password. +- **Logging Out**: + - Use the logout button if you wish to log out of your account. + + + +## SkyFly Api used + +#### Login + +```http +POST api/v1/auth/login +``` + +| Query | Type | Description | +| :-------- | :------- | :------------------------- | +| `loginRequest` | `object` | **Required**. Login request payload (email, password) | + + + +#### Register + +```http +POST api/v1/auth/register + +``` +| Query | Type | Description | +| :---------------- | :------- | :------------------------------- | +| `registerRequest` | `object` | **Required**. Register request payload (FullName, Email, PhoneNumber, password) | + + + +#### Verify Account + +```http +PUT api/v1/auth/verified +``` + +| Query | Type | Description | +| :---------------------- | :------- |:---------------------------------------------------| +| `token` | `string` | **Required**. Verification token | +| `verifyAccountRequest` | `object` | **Required**. Verify account request payload (otp) | + + + +#### Forget Password + +```http +POST api/v1/auth/forgetPassword +``` + +| Parameter | Type | Description | +| :----------------------- | :------- |:------------------------------------------------------| +| `forgetPasswordRequest` | `object` | **Required**. Forget password request payload (email) | + + +#### Get All Flight with Sort or Filter + +```http +GET api/v1/flights/ +``` + +| Query | Type | Description | +| :----------------- | :------- | :----------------------------- | +| `search` | `string` | Search query | +| `page` | `int` | Page number for pagination | +| `limit` | `int` | Number of results per page | +| `departureAirport` | `string` | Departure airport code | +| `arrivalAirport` | `string` | Arrival airport code | +| `departureDate` | `string` | Departure date (YYYY-MM-DD) | +| `returnDate` | `string` | Return date (YYYY-MM-DD) | +| `arrivalDate` | `string` | Arrival date (YYYY-MM-DD) | +| `seatClass` | `string` | Seat class | +| `adult` | `int` | Number of adult passengers | +| `children` | `int` | Number of child passengers | +| `baby` | `int` | Number of baby passengers | +| `sort` | `string` | Sorting order | + + +#### Get Flight by Id + +```http +GET api/v1/flights/{id} + +``` + +| Parameter | Type | Description | +| :---------- | :------- | :----------------------------- | +| `id` | `string` | **Required**. Flight ID | +| `seatClass` | `string` | Seat class | + + +#### Get All Airtport + +```http +GET api/v1/airports/ +``` + +| Query | Type | Description | +| :------ | :-------- | :------------------------------------ | +| `city` | `string` | City name | +| `showAll` | `boolean` | Show all airports (default: true) | + + +### Get All Flight Seat by Flight id + +```http +GET api/v1/flightSeats/flight/{id} +``` + +| Parameter | Type | Description | +| :-------- | :------- | :----------------------------- | +| `id` | `string` | **Required**. Flight ID | +| `limit` | `int` | Number of results per page | + + + + + +### Get User Profile + +```http +GET api/v1/auth/me +``` + +| Query | Type | Description | +| :-------- | :------- | :----------------------------- | +| `token` | `string` | **Required**. Verification token | + + +### Update User Profile + +```http +PATCH api/v1/auth/me +``` + +| Query | Type | Description | +| :-------- | :------- |:-------------------------------------------------------------------| +| `token` | `string` | **Required**. Verification token | +| `updateProfileRequest` | `object` | **Required**. Update profile request payload (FullName/phoneNumber | + + +### Create Transaction + +```http + POST api/v1/transactions/payment +``` + +| Query | Type | Description | +| :----------------- | :------- | :----------------------------- | +| `token` | `string` | **Required**. Verification token | +| `flightId` | `string` | **Required**. Flight ID | +| `adult` | `int` | **Required**. Number of adults | +| `child` | `int` | **Required**. Number of children | +| `baby` | `int` | **Required**. Number of babies | +| `transactionRequest` | `object` | **Required**. Transaction request payload | + +### Get All Notification +```http +GET api/v1/notifications +``` +| Query | Type | Description | +| :----- | :------- | :----------------------------- | +| `token` | `string` | **Required**. Verification token | +| `limit` | `int` | Number of results per page | + + +### Get Transaction by Id +```http + GET api/v1/transactions/{id} +``` +| Parameter | Type | Description | +| :-------- | :------- | :----------------------------- | +| `token` | `string` | **Required**. Verification token | +| `id` | `string` | **Required**. Transaction ID | + + +### Get All Transaction History, filter and search + +```http + GET api/v1/transactions +``` +| Query | Type | Description | +| :------------ | :------- | :----------------------------- | +| `token` | `string` | **Required**. Verification token | +| `limit` | `int` | Number of results per page | +| `startDate` | `string` | Start date for filtering (YYYY-MM-DD) | +| `endDate` | `string` | End date for filtering (YYYY-MM-DD) | +| `flightCode` | `string` | Flight code for filtering | + + + + + + + + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e1e27a..80b65f7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -115,6 +115,7 @@ dependencies { implementation(libs.firebase.crashlytics) implementation(libs.firebase.perf) testImplementation(libs.junit) + testImplementation(libs.junit.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -131,6 +132,8 @@ dependencies { // Local storage implementation(libs.room.ktx) + androidTestImplementation(project(":app")) + androidTestImplementation(project(":app")) ksp(libs.room.compiler) // Coroutine diff --git a/app/src/main/java/com/kom/skyfly/core/BaseActivity.kt b/app/src/main/java/com/kom/skyfly/core/BaseActivity.kt index 98c5df3..e506129 100644 --- a/app/src/main/java/com/kom/skyfly/core/BaseActivity.kt +++ b/app/src/main/java/com/kom/skyfly/core/BaseActivity.kt @@ -1,8 +1,14 @@ package com.kom.skyfly.core import android.content.Intent +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import com.kom.skyfly.R +import com.kom.skyfly.presentation.bottomsheetsdialog.NotLoginBottomSheets import com.kom.skyfly.presentation.login.LoginActivity +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException +import es.dmoral.toasty.Toasty import org.koin.androidx.viewmodel.ext.android.viewModel /** @@ -12,7 +18,20 @@ Github : https://github.com/YudaSaputraa open class BaseActivity : AppCompatActivity() { private val baseViewModel: BaseViewModel by viewModel() - fun handleUnAuthorize() { + fun errorHandler(e: Exception) { + if (e is UnAuthorizeException) { + baseViewModel.clearSession() + openNotLoggedInModal() + } else if (e is ServerErrorException) { + Toasty.error( + this, + getString(R.string.text_server_error_please_try_again_later), + Toast.LENGTH_SHORT, + ).show() + } + } + + fun doLogoutHandler() { baseViewModel.clearSession() navigateToLogin() } @@ -22,4 +41,11 @@ open class BaseActivity : AppCompatActivity() { intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK startActivity(intent) } + + private fun openNotLoggedInModal() { + if (!supportFragmentManager.isStateSaved) { + val bottomSheetFragment = NotLoginBottomSheets() + bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag) + } + } } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/auth/AuthDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/auth/AuthDataSource.kt index 6b5c1fc..45acfa8 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/auth/AuthDataSource.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/auth/AuthDataSource.kt @@ -7,6 +7,7 @@ import com.kom.skyfly.data.source.network.model.login.LoginResponse import com.kom.skyfly.data.source.network.model.register.RegisterRequest import com.kom.skyfly.data.source.network.model.register.RegisterResponse import com.kom.skyfly.data.source.network.model.resendotp.ResendOtpResponse +import com.kom.skyfly.data.source.network.model.resendotp.ResendOtpSmsRequest import com.kom.skyfly.data.source.network.model.userprofile.UserProfileResponse import com.kom.skyfly.data.source.network.model.verifyaccount.VerifyAccountRequest import com.kom.skyfly.data.source.network.model.verifyaccount.VerifyAccountResponse @@ -44,6 +45,12 @@ interface AuthDataSource { @Throws(exceptionClasses = [Exception::class]) suspend fun resendOtpRequest(token: String): ResendOtpResponse + @Throws(exceptionClasses = [Exception::class]) + suspend fun resendOtpSmsRequest( + token: String, + phoneNumber: String, + ): ResendOtpResponse + suspend fun isUserLoggedIn(): UserProfileResponse } @@ -90,6 +97,18 @@ class AuthDataSourceImpl(private val service: SkyFlyApiService) : AuthDataSource return service.resendOtp(token) } + override suspend fun resendOtpSmsRequest( + token: String, + phoneNumber: String, + ): ResendOtpResponse { + return try { + val resendOtpSmsRequest = ResendOtpSmsRequest(phoneNumber) + service.resendOtpSms(token, resendOtpSmsRequest) + } catch (e: Exception) { + throw e + } + } + override suspend fun isUserLoggedIn(): UserProfileResponse { return service.getUserProfile() } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/destinationfavorite/DestinationFavoriteDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/destinationfavorite/DestinationFavoriteDataSource.kt deleted file mode 100644 index dab6f48..0000000 --- a/app/src/main/java/com/kom/skyfly/data/datasource/destinationfavorite/DestinationFavoriteDataSource.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.kom.skyfly.data.datasource.destinationfavorite - -import com.kom.skyfly.data.model.destinationfavorite.DestinationFavorite - -/** -Written by Komang Yuda Saputra -Github : https://github.com/YudaSaputraa - **/ -interface DestinationFavoriteDataSource { - fun getAllDestinationFavorite(): List -} - -class DestinationFavoriteDataSourceImpl() : DestinationFavoriteDataSource { - override fun getAllDestinationFavorite(): List { - return mutableListOf( - DestinationFavorite( - origin = "Jakarta", - destination = "Bangkok", - airline = "AirAsia", - dateRange = "20 - 30 Maret 2023", - price = "IDR 950.000", - isLimited = false, - discount = null, - imageUrl = "https://images.unsplash.com/photo-1508009603885-50cf7c579365?q=80&w=2850&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - DestinationFavorite( - origin = "Jakarta", - destination = "Sydney", - airline = "AirAsia", - dateRange = "5 - 25 Maret 2023", - price = "IDR 3.650.000", - isLimited = false, - discount = "50% OFF", - imageUrl = "https://images.unsplash.com/photo-1506973035872-a4ec16b8e8d9?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - DestinationFavorite( - origin = "Jakarta", - destination = "Tokyo", - airline = "Garuda Indonesia", - dateRange = "1 - 10 April 2023", - price = "IDR 4.500.000", - isLimited = true, - discount = null, - imageUrl = "https://images.unsplash.com/photo-1513407030348-c983a97b98d8?q=80&w=2944&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - DestinationFavorite( - origin = "Jakarta", - destination = "Seoul", - airline = "Korean Air", - dateRange = "15 - 25 April 2023", - price = "IDR 3.800.000", - isLimited = false, - discount = "30% OFF", - imageUrl = "https://images.unsplash.com/photo-1506816561089-5cc37b3aa9b0?q=80&w=3075&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - DestinationFavorite( - origin = "Jakarta", - destination = "Paris", - airline = "Air France", - dateRange = "5 - 15 Mei 2023", - price = "IDR 8.000.000", - isLimited = true, - discount = null, - imageUrl = "https://images.unsplash.com/photo-1502602898657-3e91760cbb34?q=80&w=2946&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - DestinationFavorite( - origin = "Jakarta", - destination = "New York", - airline = "American Airlines", - dateRange = "10 - 20 Juni 2023", - price = "IDR 10.000.000", - isLimited = false, - discount = "40% OFF", - imageUrl = "https://images.unsplash.com/photo-1602828889956-45ec6cee6758?q=80&w=2832&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - DestinationFavorite( - origin = "Jakarta", - destination = "London", - airline = "British Airways", - dateRange = "20 - 30 Juni 2023", - price = "IDR 9.000.000", - isLimited = true, - discount = null, - imageUrl = "https://images.unsplash.com/photo-1513635269975-59663e0ac1ad?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - ), - ) - } -} diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/history/FlighCodeListener.kt b/app/src/main/java/com/kom/skyfly/data/datasource/history/FlighCodeListener.kt new file mode 100644 index 0000000..a5b5731 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/datasource/history/FlighCodeListener.kt @@ -0,0 +1,5 @@ +package com.kom.skyfly.data.datasource.history + +interface FlightCodeListener { + fun onFlightCodeEntered(flightCode: String) +} diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/history/HistoryDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/history/HistoryDataSource.kt index d72cba6..041fe0b 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/history/HistoryDataSource.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/history/HistoryDataSource.kt @@ -1,280 +1,26 @@ package com.kom.skyfly.data.datasource.history -import com.kom.skyfly.data.model.history.Data -import com.kom.skyfly.data.model.history.SectionedDate +import com.kom.skyfly.data.source.network.model.history.HistoryResponse +import com.kom.skyfly.data.source.network.services.SkyFlyApiService interface HistoryDataSource { - fun getHistoryData(): List - -// fun getHistoryDetailById(id: String): HistoryDetail? + suspend fun getHistoryData( + limit: Int?, + startDate: String?, + endDate: String?, + flightCode: String?, + ): HistoryResponse } -class HistoryDataSourceImpl() : HistoryDataSource { - override fun getHistoryData(): List { - return mutableListOf( - SectionedDate( - date = "Maret 2020", - data = - listOf( - Data( - status = "Unpaid", - departureLocation = "Yogyakarta", - departureDate = "2020-03-10", - departureTime = "10:00", - flightDuration = "1h 30m", - destination = "Denpasar", - arrivalDate = "2020-03-10", - arrivalTime = "11:30", - bookingCode = "GH789", - flightClass = "Business", - total = "IDR 3.000.000", - departureStatus = "On Time", - departureAirport = "Adisucipto International Airport", - departureTerminal = "Terminal 2", - airlineName = "SkyFly Airlines", - flightNumber = "SF123", - passengerName = "John Doe", - passengerId = "ABC123", - destinationAirport = "Ngurah Rai International Airport", - arrivalStatus = "On Time", - totalPassenger = "2", - price = 2500000, - tax = 50000, - ), - Data( - status = "Paid", - departureLocation = "Bandung", - departureDate = "2020-03-20", - departureTime = "13:00", - flightDuration = "1h 10m", - destination = "Surabaya", - arrivalDate = "2020-03-20", - arrivalTime = "14:10", - bookingCode = "IJ012", - flightClass = "Economy", - total = "IDR 1.200.000", - departureStatus = "Delayed", - departureAirport = "Husein Sastranegara International Airport", - departureTerminal = "Terminal 1", - airlineName = "SkyFly Airlines", - flightNumber = "SF456", - passengerName = "Jane Doe", - passengerId = "DEF456", - destinationAirport = "Juanda International Airport", - arrivalStatus = "Delayed", - totalPassenger = "1", - price = 1000000, - tax = 20000, - ), - Data( - status = "Cancelled", - departureLocation = "Jakarta", - departureDate = "2020-03-25", - departureTime = "09:30", - flightDuration = "2h 20m", - destination = "Surabaya", - arrivalDate = "2020-03-25", - arrivalTime = "11:50", - bookingCode = "MN345", - flightClass = "First", - total = "IDR 4.500.000", - departureStatus = "Cancelled", - departureAirport = "Soekarno-Hatta International Airport", - departureTerminal = "Terminal 3", - airlineName = "SkyFly Airlines", - flightNumber = "SF789", - passengerName = "Alice Smith", - passengerId = "GHI789", - destinationAirport = "Juanda International Airport", - arrivalStatus = "Cancelled", - totalPassenger = "3", - price = 4000000, - tax = 50000, - ), - ), - ), - SectionedDate( - date = "April 2020", - data = - listOf( - Data( - status = "Unpaid", - departureLocation = "Bandung", - departureDate = "2020-04-05", - departureTime = "08:00", - flightDuration = "1h 45m", - destination = "Medan", - arrivalDate = "2020-04-05", - arrivalTime = "09:45", - bookingCode = "OP678", - flightClass = "Business", - total = "IDR 3.500.000", - departureStatus = "On Time", - departureAirport = "Husein Sastranegara International Airport", - departureTerminal = "Terminal 1", - airlineName = "SkyFly Airlines", - flightNumber = "SF890", - passengerName = "Michael Johnson", - passengerId = "JKL012", - destinationAirport = "Kualanamu International Airport", - arrivalStatus = "On Time", - totalPassenger = "2", - price = 3000000, - tax = 50000, - ), - Data( - status = "Paid", - departureLocation = "Surabaya", - departureDate = "2020-04-10", - departureTime = "10:30", - flightDuration = "1h 20m", - destination = "Bandung", - arrivalDate = "2020-04-10", - arrivalTime = "11:50", - bookingCode = "QR901", - flightClass = "Economy", - total = "IDR 1.000.000", - departureStatus = "On Time", - departureAirport = "Juanda International Airport", - departureTerminal = "Terminal 2", - airlineName = "SkyFly Airlines", - flightNumber = "SF345", - passengerName = "Emily Brown", - passengerId = "MNO345", - destinationAirport = "Husein Sastranegara International Airport", - arrivalStatus = "On Time", - totalPassenger = "1", - price = 800000, - tax = 20000, - ), - Data( - status = "Cancelled", - departureLocation = "Yogyakarta", - departureDate = "2020-04-15", - departureTime = "11:00", - flightDuration = "2h", - destination = "Denpasar", - arrivalDate = "2020-04-15", - arrivalTime = "13:00", - bookingCode = "ST456", - flightClass = "First", - total = "IDR 5.000.000", - departureStatus = "Cancelled", - departureAirport = "Adisucipto International Airport", - departureTerminal = "Terminal 2", - airlineName = "SkyFly Airlines", - flightNumber = "SF678", - passengerName = "David Wilson", - passengerId = "PQR678", - destinationAirport = "Ngurah Rai International Airport", - arrivalStatus = "Cancelled", - totalPassenger = "3", - price = 4500000, - tax = 50000, - ), - ), - ), - SectionedDate( - date = "Mei 2020", - data = - listOf( - Data( - status = "Paid", - departureLocation = "Medan", - departureDate = "2020-05-05", - departureTime = "12:00", - flightDuration = "2h 30m", - destination = "Surabaya", - arrivalDate = "2020-05-05", - arrivalTime = "14:30", - bookingCode = "UV123", - flightClass = "Business", - total = "IDR 4.000.000", - departureStatus = "On Time", - departureAirport = "Kualanamu International Airport", - departureTerminal = "Terminal 1", - airlineName = "SkyFly Airlines", - flightNumber = "SF901", - passengerName = " Lisa Johnson", - passengerId = "STU901", - destinationAirport = "Juanda International Airport", - arrivalStatus = "On Time", - totalPassenger = "2", - price = 3500000, - tax = 50000, - ), - Data( - status = "Unpaid", - departureLocation = "Denpasar", - departureDate = "2020-05-10", - departureTime = "08:30", - flightDuration = "1h 15m", - destination = "Yogyakarta", - arrivalDate = "2020-05-10", - arrivalTime = "09:45", - bookingCode = "WX234", - flightClass = "Economy", - total = "IDR 1.800.000", - departureStatus = "On Time", - departureAirport = "Ngurah Rai International Airport", - departureTerminal = "Terminal 2", - airlineName = "SkyFly Airlines", - flightNumber = "SF012", - passengerName = "Chris Martin", - passengerId = "VWX012", - destinationAirport = "Adisucipto International Airport", - arrivalStatus = "On Time", - totalPassenger = "1", - price = 1500000, - tax = 30000, - ), - Data( - status = "Cancelled", - departureLocation = "Surabaya", - departureDate = "2020-05-15", - departureTime = "11:30", - flightDuration = "2h", - destination = "Bandung", - arrivalDate = "2020-05-15", - arrivalTime = "13:30", - bookingCode = "YZ345", - flightClass = "First", - total = "IDR 6.500.000", - departureStatus = "Cancelled", - departureAirport = "Juanda International Airport", - departureTerminal = "Terminal 2", - airlineName = "SkyFly Airlines", - flightNumber = "SF234", - passengerName = "Emma Watson", - passengerId = "YZA345", - destinationAirport = "Husein Sastranegara International Airport", - arrivalStatus = "Cancelled", - totalPassenger = "3", - price = 6000000, - tax = 50000, - ), - ), - ), - ) +class HistoryDataSourceImpl( + private val service: SkyFlyApiService, +) : HistoryDataSource { + override suspend fun getHistoryData( + limit: Int?, + startDate: String?, + endDate: String?, + flightCode: String?, + ): HistoryResponse { + return service.getAllTransactionHistory(limit, startDate, endDate, flightCode) } - -// override fun getHistoryDetailById(id: String): HistoryDetail? { -// val allHistoryData = getHistoryData().flatMap { it.data } -// return allHistoryData.find { it.id == id }?.let { data -> -// HistoryDetail( -// id = data.id, // Gunakan id dari data yang sesuai -// paymentStatus = data.status, -// bookingCode = data.bookingCode, -// departureTime = data.departureTime, -// departureDate = data.departureDate, -// flightClass = data.flightClass, -// arrivalTime = data.arrivalTime, -// arrivalDate = data.arrivalDate, -// total = data.total.toInt(), // Kami asumsikan total dalam format String dan perlu dikonversi ke Int -// departureLocation = data.departureLocation, -// destination = data.destination, -// flightDuration = data.flightDuration, -// ) -// } -// } } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSource.kt index bffb893..0e8c4a1 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSource.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSource.kt @@ -1,11 +1,13 @@ package com.kom.skyfly.data.datasource.home +import com.kom.skyfly.data.model.home.seat_class.SeatClassHome import com.kom.skyfly.data.source.network.model.home.airport.AirportResponse +import com.kom.skyfly.data.source.network.model.home.favourite_destination.FavouriteDestinationResponse import com.kom.skyfly.data.source.network.model.home.flight.FlightResponse import com.kom.skyfly.data.source.network.model.home.flight_detail.FlightDetailResponse interface HomeDataSource { - suspend fun getAllAirports(): AirportResponse + suspend fun getAllAirports(city: String? = null): AirportResponse suspend fun getAllFlight( search: String? = null, @@ -13,11 +15,21 @@ interface HomeDataSource { departureAirport: String, arrivalAirport: String, departureDate: String, - seatClass: String, + seatClass: String?, + limit: Int? = 20, + returnDate: String?, + arrivalDate: String? = null, + adult: Int? = 1, + children: Int? = 0, + baby: Int? = 0, ): FlightResponse suspend fun getDetailFlight( id: String, - seatClass: String, + seatClass: String?, ): FlightDetailResponse + + fun getSeatClassData(): List + + suspend fun getDestinationFavourites(): FavouriteDestinationResponse } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSourceImpl.kt b/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSourceImpl.kt index ad503f7..5cad879 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSourceImpl.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/home/HomeDataSourceImpl.kt @@ -1,6 +1,8 @@ package com.kom.skyfly.data.datasource.home +import com.kom.skyfly.data.model.home.seat_class.SeatClassHome import com.kom.skyfly.data.source.network.model.home.airport.AirportResponse +import com.kom.skyfly.data.source.network.model.home.favourite_destination.FavouriteDestinationResponse import com.kom.skyfly.data.source.network.model.home.flight.FlightResponse import com.kom.skyfly.data.source.network.model.home.flight_detail.FlightDetailResponse import com.kom.skyfly.data.source.network.services.SkyFlyApiService @@ -8,8 +10,8 @@ import com.kom.skyfly.data.source.network.services.SkyFlyApiService class HomeDataSourceImpl( private val service: SkyFlyApiService, ) : HomeDataSource { - override suspend fun getAllAirports(): AirportResponse { - return service.getAllAirports() + override suspend fun getAllAirports(city: String?): AirportResponse { + return service.getAllAirports(city = city) } override suspend fun getAllFlight( @@ -18,22 +20,50 @@ class HomeDataSourceImpl( departureAirport: String, arrivalAirport: String, departureDate: String, - seatClass: String, + seatClass: String?, + limit: Int?, + returnDate: String?, + arrivalDate: String?, + adult: Int?, + children: Int?, + baby: Int?, ): FlightResponse { return service.getAllFlights( search = search, page = page, departureAirport = departureAirport, arrivalAirport = arrivalAirport, + returnDate = returnDate, departureDate = departureDate, seatClass = seatClass, + adult = adult, + children = children, + baby = baby, ) } override suspend fun getDetailFlight( id: String, - seatClass: String, + seatClass: String?, ): FlightDetailResponse { return service.getDetailFlightById(id = id, seatClass = seatClass) } + + override fun getSeatClassData(): List { + return mutableListOf( + SeatClassHome( + seatClassName = "ECONOMY", + ), + SeatClassHome( + seatClassName = "BUSINESS", + ), + SeatClassHome( + seatClassName = "FIRST", + ), + ) + } + + override suspend fun getDestinationFavourites(): FavouriteDestinationResponse { + return service.getDestinationFavourite() + } } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/profiles/ProfileDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/profiles/ProfileDataSource.kt index 2084993..450a301 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/profiles/ProfileDataSource.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/profiles/ProfileDataSource.kt @@ -15,6 +15,7 @@ interface ProfileDataSource { suspend fun updateUserProfile( name: String?, phoneNumber: String?, + familyName: String?, password: String?, confirmPassword: String?, ): UpdateProfileResponse @@ -28,10 +29,11 @@ class ProfileDataSourceImpl(private val service: SkyFlyApiService) : ProfileData override suspend fun updateUserProfile( name: String?, phoneNumber: String?, + familyName: String?, password: String?, confirmPassword: String?, ): UpdateProfileResponse { - val updateProfileRequest = UpdateProfileRequest(name, phoneNumber, password, confirmPassword) + val updateProfileRequest = UpdateProfileRequest(name, phoneNumber, familyName, password, confirmPassword) return service.updateUserProfile(updateProfileRequest) } } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/searchhistory/SearchHistoryDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/searchhistory/SearchHistoryDataSource.kt index a557f56..aa465b4 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/searchhistory/SearchHistoryDataSource.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/searchhistory/SearchHistoryDataSource.kt @@ -1,6 +1,7 @@ package com.kom.skyfly.data.datasource.searchhistory import com.kom.skyfly.data.source.local.database.dao.SearchHistoryDao +import com.kom.skyfly.data.source.local.database.entity.SearchDestinationHistoryEntity import com.kom.skyfly.data.source.local.database.entity.SearchHistoryEntity import kotlinx.coroutines.flow.Flow @@ -11,23 +12,52 @@ Github : https://github.com/YudaSaputraa interface SearchHistoryDataSource { fun getAllSearchHistory(): Flow> + fun getAllSearchDestinationHistory(): Flow> + suspend fun insertSearchHistory(searchHistory: SearchHistoryEntity): Long + suspend fun insertSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Long + suspend fun updateSearchHistory(searchHistory: SearchHistoryEntity): Int + suspend fun updateSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Int + suspend fun deleteSearchHistory(searchHistory: SearchHistoryEntity): Int + suspend fun deleteSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Int + suspend fun deleteAllSearchHistory() + + suspend fun deleteAllSearchDestinationHistory() } class SearchHistoryDataSourceImpl(private val dao: SearchHistoryDao) : SearchHistoryDataSource { override fun getAllSearchHistory(): Flow> = dao.getAllSearchHistory() + override fun getAllSearchDestinationHistory(): Flow> = dao.getAllSearchDestinationHistory() + override suspend fun insertSearchHistory(searchHistory: SearchHistoryEntity): Long = dao.insertSearchHistory(searchHistory) + override suspend fun insertSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Long = + dao.insertSearchDestinationHistory( + searchDestinationHistory = searchDestinationHistory, + ) + override suspend fun updateSearchHistory(searchHistory: SearchHistoryEntity): Int = dao.updateSearchHistory(searchHistory) + override suspend fun updateSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Int = + dao.updateSearchDestinationHistory( + searchDestinationHistory = searchDestinationHistory, + ) + override suspend fun deleteSearchHistory(searchHistory: SearchHistoryEntity): Int = dao.deleteSearchHistory(searchHistory) + override suspend fun deleteSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Int = + dao.deleteSearchDestinationHistory( + searchDestinationHistory = searchDestinationHistory, + ) + override suspend fun deleteAllSearchHistory() = dao.deleteAll() + + override suspend fun deleteAllSearchDestinationHistory() = dao.deleteAllRecentSearchDestination() } diff --git a/app/src/main/java/com/kom/skyfly/data/datasource/transaction/TransactionDataSource.kt b/app/src/main/java/com/kom/skyfly/data/datasource/transaction/TransactionDataSource.kt index 78bc8f8..8932853 100644 --- a/app/src/main/java/com/kom/skyfly/data/datasource/transaction/TransactionDataSource.kt +++ b/app/src/main/java/com/kom/skyfly/data/datasource/transaction/TransactionDataSource.kt @@ -1,5 +1,7 @@ package com.kom.skyfly.data.datasource.transaction +import com.kom.skyfly.data.source.network.model.paymentstatus.PaymentStatusResponse +import com.kom.skyfly.data.source.network.model.transaction.cancel.CancelTransactionResponse import com.kom.skyfly.data.source.network.model.transaction.detail.TransactionDetailResponse import com.kom.skyfly.data.source.network.model.transaction.request.TransactionRequest import com.kom.skyfly.data.source.network.model.transaction.response.TransactionResponse @@ -19,6 +21,10 @@ interface TransactionDataSource { ): TransactionResponse suspend fun getTransactionById(id: String): TransactionDetailResponse + + suspend fun getPaymentStatus(id: String): PaymentStatusResponse + + suspend fun cancelTransaction(id: String): CancelTransactionResponse } class TransactionDataSourceImpl(private val service: SkyFlyApiService) : TransactionDataSource { @@ -35,4 +41,12 @@ class TransactionDataSourceImpl(private val service: SkyFlyApiService) : Transac override suspend fun getTransactionById(id: String): TransactionDetailResponse { return service.getTransactionById(id) } + + override suspend fun getPaymentStatus(id: String): PaymentStatusResponse { + return service.getPaymentStatus(id) + } + + override suspend fun cancelTransaction(id: String): CancelTransactionResponse { + return service.cancelTransaction(id) + } } diff --git a/app/src/main/java/com/kom/skyfly/data/mapper/CancelTransactionMapper.kt b/app/src/main/java/com/kom/skyfly/data/mapper/CancelTransactionMapper.kt new file mode 100644 index 0000000..4332233 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/mapper/CancelTransactionMapper.kt @@ -0,0 +1,21 @@ +package com.kom.skyfly.data.mapper + +import com.kom.skyfly.data.model.transaction.cancel.ItemsCancelTransactionModel +import com.kom.skyfly.data.source.network.model.transaction.cancel.ItemsCancelTransactionResponse + +/** +Written by Komang Yuda Saputra +Github : https://github.com/YudaSaputraa + **/ +fun ItemsCancelTransactionResponse?.toCancelTransaction() = + ItemsCancelTransactionModel( + currency = this?.currency.orEmpty(), + grossAmount = this?.grossAmount.orEmpty(), + merchantId = this?.merchantId.orEmpty(), + orderId = this?.orderId.orEmpty(), + paymentStatus = this?.paymentStatus.orEmpty(), + paymentType = this?.paymentType.orEmpty(), + transactionId = this?.transactionId.orEmpty(), + transactionStatus = this?.transactionStatus.orEmpty(), + transactionTime = this?.transactionTime.orEmpty(), + ) diff --git a/app/src/main/java/com/kom/skyfly/data/mapper/HistoryTransactionMapper.kt b/app/src/main/java/com/kom/skyfly/data/mapper/HistoryTransactionMapper.kt new file mode 100644 index 0000000..07ba087 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/mapper/HistoryTransactionMapper.kt @@ -0,0 +1,170 @@ +package com.kom.skyfly.data.mapper + +import com.kom.skyfly.data.model.history.new.AirlineDomain +import com.kom.skyfly.data.model.history.new.ArrivalDomain +import com.kom.skyfly.data.model.history.new.BookingDomain +import com.kom.skyfly.data.model.history.new.DepartureAirportDomain +import com.kom.skyfly.data.model.history.new.DepartureDomain +import com.kom.skyfly.data.model.history.new.DestinationAirportDomain +import com.kom.skyfly.data.model.history.new.FlightDomain +import com.kom.skyfly.data.model.history.new.HistoryDomain +import com.kom.skyfly.data.model.history.new.ItemsHistoryDomain +import com.kom.skyfly.data.model.history.new.SeatDomain +import com.kom.skyfly.data.model.history.new.TransactionDetailDomain +import com.kom.skyfly.data.model.history.new.TransactionDomain +import com.kom.skyfly.data.source.network.model.history.Airline +import com.kom.skyfly.data.source.network.model.history.Arrival +import com.kom.skyfly.data.source.network.model.history.Booking +import com.kom.skyfly.data.source.network.model.history.Departure +import com.kom.skyfly.data.source.network.model.history.DepartureAirport +import com.kom.skyfly.data.source.network.model.history.DestinationAirport +import com.kom.skyfly.data.source.network.model.history.Flight +import com.kom.skyfly.data.source.network.model.history.HistoryResponse +import com.kom.skyfly.data.source.network.model.history.ItemsHistoryResponse +import com.kom.skyfly.data.source.network.model.history.Seat +import com.kom.skyfly.data.source.network.model.history.Transaction +import com.kom.skyfly.data.source.network.model.history.TransactionDetail + +fun HistoryResponse?.toHistoryDomain(): HistoryDomain = + this?.let { + HistoryDomain( + data = it.data.toItemsHistoryDomainList(), + message = it.message.orEmpty(), + status = it.status ?: false, + totalItems = it.totalItems ?: 0, + ) + } ?: HistoryDomain(emptyList(), "", false, 0) + +fun List?.toItemsHistoryDomainList(): List = + this?.mapNotNull { it.toItemsHistoryDomain() } ?: emptyList() + +fun ItemsHistoryResponse?.toItemsHistoryDomain(): ItemsHistoryDomain? = + this?.let { + ItemsHistoryDomain( + date = it.date.orEmpty(), + transactions = it.transactions.toTransactionDomainList(), + ) + } + +fun List?.toTransactionDomainList(): List = this?.mapNotNull { it.toTransactionDomain() } ?: emptyList() + +fun Transaction?.toTransactionDomain(): TransactionDomain? = + this?.let { + TransactionDomain( + booking = it.booking.toBookingDomain(), + id = it.id.orEmpty(), + orderId = it.orderId.orEmpty(), + status = it.status.orEmpty(), + tax = it.tax ?: 0, + totalPrice = it.totalPrice ?: 0, + transactionDetail = it.transactionDetail.toTransactionDetailDomainList(), + userId = it.userId.orEmpty(), + ) + } + +fun List?.toTransactionDetailDomainList(): List = + this?.mapNotNull { it.toTransactionDetailDomain() } ?: emptyList() + +fun TransactionDetail?.toTransactionDetailDomain(): TransactionDetailDomain? = + this?.let { + it.flight.toFlightDomain()?.let { flight -> + TransactionDetailDomain( + citizenship = it.citizenship.orEmpty(), + dob = it.dob.orEmpty(), + familyName = it.familyName.orEmpty(), + flight = flight, + id = it.id.orEmpty(), + issuingCountry = it.issuingCountry.orEmpty(), + name = it.name.orEmpty(), + passengerCategory = it.passengerCategory.orEmpty(), + passport = it.passport.orEmpty(), + seat = it.seat.toSeatDomain(), + totalPrice = it.totalPrice ?: 0, + transactionId = it.transactionId.orEmpty(), + validityPeriod = it.validityPeriod.orEmpty(), + ) + } + } + +fun Airline?.toAirlineDomain(): AirlineDomain = + AirlineDomain( + code = this?.code.orEmpty(), + id = this?.id.orEmpty(), + image = this?.image.orEmpty(), + name = this?.name.orEmpty(), + terminal = this?.terminal.orEmpty(), + ) + +fun Arrival?.toArrivalDomain(): ArrivalDomain = + ArrivalDomain( + date = this?.date.orEmpty(), + time = this?.time.orEmpty(), + ) + +fun Booking?.toBookingDomain(): BookingDomain = + BookingDomain( + code = this?.code.orEmpty(), + date = this?.date.orEmpty(), + time = this?.time.orEmpty(), + ) + +fun Departure?.toDepartureDomain(): DepartureDomain = + DepartureDomain( + date = this?.date.orEmpty(), + time = this?.time.orEmpty(), + ) + +fun DepartureAirport?.toDepartureAirportDomain(): DepartureAirportDomain = + DepartureAirportDomain( + city = this?.city.orEmpty(), + code = this?.code.orEmpty(), + continent = this?.continent.orEmpty(), + country = this?.country.orEmpty(), + id = this?.id.orEmpty(), + image = this?.image.orEmpty(), + name = this?.name.orEmpty(), + ) + +fun DestinationAirport?.toDestinationAirportDomain(): DestinationAirportDomain = + DestinationAirportDomain( + city = this?.city.orEmpty(), + code = this?.code.orEmpty(), + continent = this?.continent.orEmpty(), + country = this?.country.orEmpty(), + id = this?.id.orEmpty(), + image = this?.image.orEmpty(), + name = this?.name.orEmpty(), + ) + +fun Flight?.toFlightDomain(): FlightDomain? = + this?.airline?.toAirlineDomain()?.let { + this.arrival?.toArrivalDomain()?.let { arrival -> + this.departure?.toDepartureDomain()?.let { departure -> + this.departureAirport?.toDepartureAirportDomain()?.let { departureAirport -> + this.destinationAirport?.toDestinationAirportDomain()?.let { destinationAirport -> + FlightDomain( + airline = it, + arrival = arrival, + code = this.code.orEmpty(), + departure = departure, + departureAirport = departureAirport, + destinationAirport = destinationAirport, + flightDuration = this.flightDuration.orEmpty(), + flightPrice = this.flightPrice ?: 0, + id = this.id.orEmpty(), + ) + } + } + } + } + } + +fun Seat?.toSeatDomain(): SeatDomain = + SeatDomain( + flightId = this?.flightId.orEmpty(), + id = this?.id.orEmpty(), + seatNumber = this?.seatNumber.orEmpty(), + seatPrice = this?.seatPrice ?: 0, + status = this?.status.orEmpty(), + type = this?.type.orEmpty(), + ) diff --git a/app/src/main/java/com/kom/skyfly/data/mapper/PaymentStatusMapper.kt b/app/src/main/java/com/kom/skyfly/data/mapper/PaymentStatusMapper.kt new file mode 100644 index 0000000..688a44e --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/mapper/PaymentStatusMapper.kt @@ -0,0 +1,49 @@ +package com.kom.skyfly.data.mapper + +import com.kom.skyfly.data.model.transaction.paymentstatus.ItemsPaymentStatus +import com.kom.skyfly.data.model.transaction.paymentstatus.PaymentStatusModel +import com.kom.skyfly.data.model.transaction.paymentstatus.VaNumberModel +import com.kom.skyfly.data.source.network.model.paymentstatus.ItemsPaymentStatusResponse +import com.kom.skyfly.data.source.network.model.paymentstatus.PaymentStatusResponse +import com.kom.skyfly.data.source.network.model.paymentstatus.VaNumber + +/** +Written by Komang Yuda Saputra +Github : https://github.com/YudaSaputraa + **/ +fun PaymentStatusResponse?.toPaymentStatus(): PaymentStatusModel = + this?.let { + PaymentStatusModel( + data = it.data.toItemsPaymentStatusDomain(), + message = it.message.orEmpty(), + status = it.status ?: false, + ) + } ?: PaymentStatusModel(null, "", false) + +fun ItemsPaymentStatusResponse?.toItemsPaymentStatusDomain(): ItemsPaymentStatus? = + this?.let { + ItemsPaymentStatus( + currency = it.currency.orEmpty(), + expiryTime = it.expiryTime.orEmpty(), + grossAmount = it.grossAmount.orEmpty(), + merchantId = it.merchantId.orEmpty(), + orderId = it.orderId.orEmpty(), + paymentStatus = it.paymentStatus.orEmpty(), + paymentType = it.paymentType.orEmpty(), + signatureKey = it.signatureKey.orEmpty(), + transactionId = it.transactionId.orEmpty(), + transactionStatus = it.transactionStatus.orEmpty(), + transactionTime = it.transactionTime.orEmpty(), + vaNumbers = it.vaNumbers.toVaNumberDomainList(), + ) + } + +fun List?.toVaNumberDomainList(): List = this?.mapNotNull { it.toVaNumberDomain() } ?: emptyList() + +fun VaNumber?.toVaNumberDomain(): VaNumberModel? = + this?.let { + VaNumberModel( + bank = it.bank.orEmpty(), + vaNumber = it.vaNumber.orEmpty(), + ) + } diff --git a/app/src/main/java/com/kom/skyfly/data/mapper/SearchHistoryMapper.kt b/app/src/main/java/com/kom/skyfly/data/mapper/SearchHistoryMapper.kt index 03bd354..444f1f5 100644 --- a/app/src/main/java/com/kom/skyfly/data/mapper/SearchHistoryMapper.kt +++ b/app/src/main/java/com/kom/skyfly/data/mapper/SearchHistoryMapper.kt @@ -1,6 +1,8 @@ package com.kom.skyfly.data.mapper +import com.kom.skyfly.data.model.home.search_history.SearchDestinationHistory import com.kom.skyfly.data.model.searchhistory.SearchHistory +import com.kom.skyfly.data.source.local.database.entity.SearchDestinationHistoryEntity import com.kom.skyfly.data.source.local.database.entity.SearchHistoryEntity /** @@ -21,4 +23,18 @@ fun SearchHistoryEntity?.toSearchHistory() = searchHistory = this?.searchHistory.orEmpty(), ) +fun SearchDestinationHistory?.toSearchDestinationHistoryEntity() = + SearchDestinationHistoryEntity( + id = this?.id ?: 0, + searchDestinationHistory = this?.searchDestinationHistory.orEmpty(), + ) + +fun SearchDestinationHistoryEntity?.toSearchDestinationHistory() = + SearchDestinationHistory( + id = this?.id ?: 0, + searchDestinationHistory = this?.searchDestinationHistory.orEmpty(), + ) + fun List?.toSearchHistoryList() = this?.map { it.toSearchHistory() } + +fun List?.toSearchDestinationHistoryList() = this?.map { it.toSearchDestinationHistory() } diff --git a/app/src/main/java/com/kom/skyfly/data/mapper/home/FlightTicketMapper.kt b/app/src/main/java/com/kom/skyfly/data/mapper/home/FlightTicketMapper.kt index a3f3cc2..1133cf6 100644 --- a/app/src/main/java/com/kom/skyfly/data/mapper/home/FlightTicketMapper.kt +++ b/app/src/main/java/com/kom/skyfly/data/mapper/home/FlightTicketMapper.kt @@ -1,8 +1,10 @@ package com.kom.skyfly.data.mapper.home +import com.kom.skyfly.data.model.home.destination_favourite.DestinationFavourite import com.kom.skyfly.data.model.home.flight.FlightTicket import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket import com.kom.skyfly.data.source.network.model.common.FlightData +import com.kom.skyfly.data.source.network.model.home.favourite_destination.FlightDetailResponse import com.kom.skyfly.data.source.network.model.home.flight_detail.FlightDetailData fun FlightData?.toFlightTicket() = @@ -24,6 +26,9 @@ fun FlightData?.toFlightTicket() = airplaneImg = this?.plane?.image.orEmpty(), facilities = this?.facilities.orEmpty(), departureTerminal = this?.plane?.terminal.orEmpty(), + code = this?.plane?.code.orEmpty(), + departureDate = this?.departureDate.orEmpty(), + arrivalDate = this?.arrivalDate.orEmpty(), ) fun FlightDetailData?.toFlightDetailTicket() = @@ -39,8 +44,8 @@ fun FlightDetailData?.toFlightDetailTicket() = arrivalTime = this?.arrivalTime.orEmpty(), transitNotes = this?.transit?.status ?: false, duration = this?.duration.orEmpty(), - price = this?.price ?: 0, - seatClass = "ECONOMY", + price = this?.seatClass?.first()?.seatPrice ?: 0, + seatClass = this?.seatClass?.first()?.seatClassName.orEmpty(), airplaneName = this?.plane?.name.orEmpty(), airplaneImg = this?.plane?.image.orEmpty(), facilities = @@ -54,6 +59,20 @@ fun FlightDetailData?.toFlightDetailTicket() = code = this?.code.orEmpty(), ) +fun FlightDetailResponse?.toDestinationFavourite() = + DestinationFavourite( + id = this?.flightDetails?.flightId.orEmpty(), + departureDate = this?.flightDetails?.sourceDestination?.departureDate.orEmpty(), + departureCity = this?.flightDetails?.sourceDestination?.departureCity.orEmpty(), + arrivalDate = this?.flightDetails?.arrivalDestination?.arrivalDate.orEmpty(), + arrivalCity = this?.flightDetails?.arrivalDestination?.arrivalCity.orEmpty(), + price = this?.flightDetails?.plane?.price ?: 0, + img = this?.flightDetails?.arrivalDestination?.image.orEmpty(), + airline = this?.flightDetails?.plane?.airline.orEmpty(), + ) + fun Collection?.toFlightTickets() = this?.map { it.toFlightTicket() } ?: listOf() fun Collection?.toFlightDetailTickets() = this?.map { it.toFlightDetailTicket() } ?: listOf() + +fun Collection?.toDestinationFavourites() = this?.map { it.toDestinationFavourite() } ?: listOf() diff --git a/app/src/main/java/com/kom/skyfly/data/model/destinationfavorite/DestinationFavorite.kt b/app/src/main/java/com/kom/skyfly/data/model/destinationfavorite/DestinationFavorite.kt deleted file mode 100644 index 9a089ec..0000000 --- a/app/src/main/java/com/kom/skyfly/data/model/destinationfavorite/DestinationFavorite.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.kom.skyfly.data.model.destinationfavorite - -import java.util.UUID - -/** -Written by Komang Yuda Saputra -Github : https://github.com/YudaSaputraa - **/ -data class DestinationFavorite( - val id: String? = UUID.randomUUID().toString(), - val origin: String?, - val destination: String?, - val airline: String?, - val dateRange: String?, - val price: String?, - val isLimited: Boolean?, - val discount: String?, - val imageUrl: String?, -) diff --git a/app/src/main/java/com/kom/skyfly/data/model/history/Data.kt b/app/src/main/java/com/kom/skyfly/data/model/history/Data.kt deleted file mode 100644 index 08945f9..0000000 --- a/app/src/main/java/com/kom/skyfly/data/model/history/Data.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.kom.skyfly.data.model.history - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import java.util.UUID - -@Parcelize -data class Data( - val id: String = UUID.randomUUID().toString(), - val status: String, - val departureLocation: String, - val departureDate: String, - val departureTime: String, - val flightDuration: String, - val destination: String, - val arrivalDate: String, - val arrivalTime: String, - val bookingCode: String, - val flightClass: String, - val total: String, - val departureStatus: String, - val departureAirport: String, - val departureTerminal: String, - val airlineName: String, - val flightNumber: String, - val passengerName: String, - val passengerId: String, - val destinationAirport: String, - val arrivalStatus: String, - val totalPassenger: String, - val price: Int, - val tax: Int, -) : Parcelable diff --git a/app/src/main/java/com/kom/skyfly/data/model/history/SectionedDate.kt b/app/src/main/java/com/kom/skyfly/data/model/history/SectionedDate.kt deleted file mode 100644 index 5321d43..0000000 --- a/app/src/main/java/com/kom/skyfly/data/model/history/SectionedDate.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.kom.skyfly.data.model.history - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -data class SectionedDate( - val date: String, - val data: List, -) : Parcelable diff --git a/app/src/main/java/com/kom/skyfly/data/model/history/new/new.kt b/app/src/main/java/com/kom/skyfly/data/model/history/new/new.kt new file mode 100644 index 0000000..4245245 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/history/new/new.kt @@ -0,0 +1,92 @@ +package com.kom.skyfly.data.model.history.new + +data class AirlineDomain( + val code: String, + val id: String, + val image: String, + val name: String, + val terminal: String, +) + +data class ArrivalDomain(val date: String, val time: String) + +data class BookingDomain(val code: String, val date: String, val time: String) + +data class DepartureDomain(val date: String, val time: String) + +data class DepartureAirportDomain( + val city: String, + val code: String, + val continent: String, + val country: String, + val id: String, + val image: String, + val name: String, +) + +data class DestinationAirportDomain( + val city: String, + val code: String, + val continent: String, + val country: String, + val id: String, + val image: String, + val name: String, +) + +data class FlightDomain( + val airline: AirlineDomain, + val arrival: ArrivalDomain, + val code: String, + val departure: DepartureDomain, + val departureAirport: DepartureAirportDomain, + val destinationAirport: DestinationAirportDomain, + val flightDuration: String, + val flightPrice: Int, + val id: String, +) + +data class ItemsHistoryDomain(val date: String, val transactions: List) + +data class SeatDomain( + val flightId: String, + val id: String, + val seatNumber: String, + val seatPrice: Int, + val status: String, + val type: String, +) + +data class TransactionDomain( + val booking: BookingDomain, + val id: String, + val orderId: String, + val status: String, + val tax: Int, + val totalPrice: Int, + val transactionDetail: List, + val userId: String, +) + +data class TransactionDetailDomain( + val citizenship: String, + val dob: String, + val familyName: String, + val flight: FlightDomain, + val id: String, + val issuingCountry: String, + val name: String, + val passengerCategory: String, + val passport: String, + val seat: SeatDomain, + val totalPrice: Int, + val transactionId: String, + val validityPeriod: String, +) + +data class HistoryDomain( + val data: List, + val message: String, + val status: Boolean, + val totalItems: Int, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/home/destination_favourite/DestinationFavourite.kt b/app/src/main/java/com/kom/skyfly/data/model/home/destination_favourite/DestinationFavourite.kt new file mode 100644 index 0000000..4e2cbfe --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/home/destination_favourite/DestinationFavourite.kt @@ -0,0 +1,21 @@ +package com.kom.skyfly.data.model.home.destination_favourite + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** +Written by Komang Yuda Saputra +Github : https://github.com/YudaSaputraa + **/ + +@Parcelize +data class DestinationFavourite( + val id: String?, + val departureDate: String?, + val departureCity: String?, + val arrivalDate: String?, + val arrivalCity: String?, + val airline: String?, + val price: Int?, + val img: String?, +) : Parcelable diff --git a/app/src/main/java/com/kom/skyfly/data/model/home/flight/FlightTicket.kt b/app/src/main/java/com/kom/skyfly/data/model/home/flight/FlightTicket.kt index f6511fe..349d1c1 100644 --- a/app/src/main/java/com/kom/skyfly/data/model/home/flight/FlightTicket.kt +++ b/app/src/main/java/com/kom/skyfly/data/model/home/flight/FlightTicket.kt @@ -7,6 +7,8 @@ import kotlinx.parcelize.Parcelize data class FlightTicket( val id: String, val departureCity: String, + val departureDate: String, + val arrivalDate: String, val arrivalCity: String, val departureAirport: String, val arrivalAirport: String, @@ -17,6 +19,7 @@ data class FlightTicket( val directNotes: Boolean, val duration: String, val price: Int, + val code: String, val seatClass: String, val airplaneName: String, val airplaneImg: String, diff --git a/app/src/main/java/com/kom/skyfly/data/model/home/flight_detail/FlightDetailTicket.kt b/app/src/main/java/com/kom/skyfly/data/model/home/flight_detail/FlightDetailTicket.kt index e556a5e..78b9984 100644 --- a/app/src/main/java/com/kom/skyfly/data/model/home/flight_detail/FlightDetailTicket.kt +++ b/app/src/main/java/com/kom/skyfly/data/model/home/flight_detail/FlightDetailTicket.kt @@ -1,5 +1,9 @@ package com.kom.skyfly.data.model.home.flight_detail +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize data class FlightDetailTicket( val id: String, val departureCity: String, @@ -21,4 +25,4 @@ data class FlightDetailTicket( val code: String, val facilities: String, val departureTerminal: String, -) +) : Parcelable diff --git a/app/src/main/java/com/kom/skyfly/data/model/home/intent/SearchResultIntent.kt b/app/src/main/java/com/kom/skyfly/data/model/home/intent/SearchResultIntent.kt new file mode 100644 index 0000000..28f4e5f --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/home/intent/SearchResultIntent.kt @@ -0,0 +1,15 @@ +package com.kom.skyfly.data.model.home.intent + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class SearchResultIntent( + val returnId: String?, + val departureId: String?, + val seatClass: String?, + val adultCount: Int?, + val childCount: Int?, + val babyCount: Int?, + val roundTrip: Boolean?, +) : Parcelable diff --git a/app/src/main/java/com/kom/skyfly/data/model/home/search_history/SearchDestinationHistory.kt b/app/src/main/java/com/kom/skyfly/data/model/home/search_history/SearchDestinationHistory.kt new file mode 100644 index 0000000..f0e60e6 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/home/search_history/SearchDestinationHistory.kt @@ -0,0 +1,6 @@ +package com.kom.skyfly.data.model.home.search_history + +data class SearchDestinationHistory( + var id: Int? = null, + var searchDestinationHistory: String? = null, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/home/seat_class/SeatClassHome.kt b/app/src/main/java/com/kom/skyfly/data/model/home/seat_class/SeatClassHome.kt new file mode 100644 index 0000000..8aac0c4 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/home/seat_class/SeatClassHome.kt @@ -0,0 +1,8 @@ +package com.kom.skyfly.data.model.home.seat_class + +import java.util.UUID + +data class SeatClassHome( + val id: String = UUID.randomUUID().toString(), + val seatClassName: String, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/transaction/cancel/CancelTransactionModel.kt b/app/src/main/java/com/kom/skyfly/data/model/transaction/cancel/CancelTransactionModel.kt new file mode 100644 index 0000000..2019a1b --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/transaction/cancel/CancelTransactionModel.kt @@ -0,0 +1,14 @@ +package com.kom.skyfly.data.model.transaction.cancel + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class CancelTransactionModel( + @SerializedName("data") + val data: ItemsCancelTransactionModel?, + @SerializedName("message") + val message: String?, + @SerializedName("status") + val status: Boolean?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/transaction/cancel/ItemsCancelTransactionModel.kt b/app/src/main/java/com/kom/skyfly/data/model/transaction/cancel/ItemsCancelTransactionModel.kt new file mode 100644 index 0000000..8afdd17 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/transaction/cancel/ItemsCancelTransactionModel.kt @@ -0,0 +1,26 @@ +package com.kom.skyfly.data.model.transaction.cancel + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class ItemsCancelTransactionModel( + @SerializedName("currency") + val currency: String?, + @SerializedName("gross_amount") + val grossAmount: String?, + @SerializedName("merchant_id") + val merchantId: String?, + @SerializedName("order_id") + val orderId: String?, + @SerializedName("payment_status") + val paymentStatus: String?, + @SerializedName("payment_type") + val paymentType: String?, + @SerializedName("transaction_id") + val transactionId: String?, + @SerializedName("transaction_status") + val transactionStatus: String?, + @SerializedName("transaction_time") + val transactionTime: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/ItemsPaymentStatus.kt b/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/ItemsPaymentStatus.kt new file mode 100644 index 0000000..981113b --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/ItemsPaymentStatus.kt @@ -0,0 +1,19 @@ +package com.kom.skyfly.data.model.transaction.paymentstatus + +import androidx.annotation.Keep + +@Keep +data class ItemsPaymentStatus( + val currency: String?, + val expiryTime: String?, + val grossAmount: String?, + val merchantId: String?, + val orderId: String?, + val paymentStatus: String?, + val paymentType: String?, + val signatureKey: String?, + val transactionId: String?, + val transactionStatus: String?, + val transactionTime: String?, + val vaNumbers: List?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/PaymentStatusModel.kt b/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/PaymentStatusModel.kt new file mode 100644 index 0000000..5539841 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/PaymentStatusModel.kt @@ -0,0 +1,10 @@ +package com.kom.skyfly.data.model.transaction.paymentstatus + +import androidx.annotation.Keep + +@Keep +data class PaymentStatusModel( + val data: ItemsPaymentStatus?, + val message: String?, + val status: Boolean?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/VaNumberModel.kt b/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/VaNumberModel.kt new file mode 100644 index 0000000..a6b16d0 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/model/transaction/paymentstatus/VaNumberModel.kt @@ -0,0 +1,12 @@ +package com.kom.skyfly.data.model.transaction.paymentstatus + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class VaNumberModel( + @SerializedName("bank") + val bank: String?, + @SerializedName("va_number") + val vaNumber: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/repository/auth/AuthRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/auth/AuthRepository.kt index 63aed99..e94bfc6 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/auth/AuthRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/auth/AuthRepository.kt @@ -44,6 +44,12 @@ interface AuthRepository { @Throws(exceptionClasses = [Exception::class]) fun resendOtpRequest(token: String): Flow> + @Throws(exceptionClasses = [Exception::class]) + fun resendOtpSmsRequest( + token: String, + phoneNumber: String, + ): Flow> + fun isUserLoggedIn(): Flow> } @@ -81,6 +87,13 @@ class AuthRepositoryImpl(private val dataSource: AuthDataSource) : AuthRepositor return proceedFlow { dataSource.resendOtpRequest(token) } } + override fun resendOtpSmsRequest( + token: String, + phoneNumber: String, + ): Flow> { + return proceedFlow { dataSource.resendOtpSmsRequest(token, phoneNumber) } + } + override fun isUserLoggedIn(): Flow> { return proceedFlow { dataSource.isUserLoggedIn().toUserIsLoggedIn() } } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/destinationfavorite/DestinationFavoriteRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/destinationfavorite/DestinationFavoriteRepository.kt deleted file mode 100644 index cf6dd94..0000000 --- a/app/src/main/java/com/kom/skyfly/data/repository/destinationfavorite/DestinationFavoriteRepository.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.kom.skyfly.data.repository.destinationfavorite - -import com.kom.skyfly.data.datasource.destinationfavorite.DestinationFavoriteDataSource -import com.kom.skyfly.data.model.destinationfavorite.DestinationFavorite -import com.kom.skyfly.utils.ResultWrapper -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow - -/** -Written by Komang Yuda Saputra -Github : https://github.com/YudaSaputraa - **/ -interface DestinationFavoriteRepository { - fun getAllDestinationFavorite(): Flow>> -} - -class DestinationFavoriteRepositoryImpl(private val destinationFavoriteDataSource: DestinationFavoriteDataSource) : - DestinationFavoriteRepository { - override fun getAllDestinationFavorite(): Flow>> { - return flow { - emit(ResultWrapper.Loading()) - delay(1000) - val result = destinationFavoriteDataSource.getAllDestinationFavorite() - emit(ResultWrapper.Success(result)) - } - } -} diff --git a/app/src/main/java/com/kom/skyfly/data/repository/history/HistoryRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/history/HistoryRepository.kt index 54d24f3..b2e798b 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/history/HistoryRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/history/HistoryRepository.kt @@ -1,38 +1,31 @@ package com.kom.skyfly.data.repository.history import com.kom.skyfly.data.datasource.history.HistoryDataSource -import com.kom.skyfly.data.model.history.SectionedDate +import com.kom.skyfly.data.mapper.toHistoryDomain +import com.kom.skyfly.data.model.history.new.HistoryDomain import com.kom.skyfly.utils.ResultWrapper -import kotlinx.coroutines.delay +import com.kom.skyfly.utils.proceedFlow import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow interface HistoryRepository { - fun getHistoryData(): Flow>> - -// fun getDetailHistoryById(id: Int): Flow> + fun getHistoryData( + limit: Int?, + startDate: String?, + endDate: String?, + flightCode: String?, + ): Flow> } -class HistoryRepositoryImpl(private val historyDataSource: HistoryDataSource) : - HistoryRepository { - override fun getHistoryData(): Flow>> { - return flow { - emit(ResultWrapper.Loading()) - delay(2000) - val result = historyDataSource.getHistoryData() - emit(ResultWrapper.Success(result)) +class HistoryRepositoryImpl(private val historyDataSource: HistoryDataSource) : HistoryRepository { + override fun getHistoryData( + limit: Int?, + startDate: String?, + endDate: String?, + flightCode: String?, + ): Flow> { + return proceedFlow { + historyDataSource.getHistoryData(limit, startDate, endDate, flightCode) + .toHistoryDomain() } } - -// override fun getDetailHistoryById(id: Int): Flow> { -// return flow { -// emit(ResultWrapper.Loading()) -// val historyDetail = historyDataSource.getHistoryDetailById(id.toString()) -// if (historyDetail != null) { -// emit(ResultWrapper.Success(historyDetail)) -// } else { -// emit(ResultWrapper.Error(Exception("History detail not found"))) -// } -// } -// } } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepository.kt index dec666a..dc8607f 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepository.kt @@ -1,9 +1,18 @@ package com.kom.skyfly.data.repository.home.airport import com.kom.skyfly.data.model.home.search.Airport +import com.kom.skyfly.data.model.home.search_history.SearchDestinationHistory import com.kom.skyfly.utils.ResultWrapper import kotlinx.coroutines.flow.Flow interface AirportRepository { - fun getAllAirportData(): Flow>> + fun getAllAirportData(city: String? = null): Flow>> + + fun createSearchDestinationHistory(searchDestinationHistory: String): Flow> + + fun deleteSearchDestinationHistory(item: SearchDestinationHistory): Flow> + + fun deleteAllSearchDestinationHistory(): Flow> + + fun getUserSearchDestinationHistory(): Flow>> } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepositoryImpl.kt b/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepositoryImpl.kt index 0070f00..96adb09 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepositoryImpl.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/home/airport/AirportRepositoryImpl.kt @@ -1,21 +1,65 @@ package com.kom.skyfly.data.repository.home.airport import com.kom.skyfly.data.datasource.home.HomeDataSource +import com.kom.skyfly.data.datasource.searchhistory.SearchHistoryDataSource import com.kom.skyfly.data.mapper.home.toAirports +import com.kom.skyfly.data.mapper.toSearchDestinationHistoryEntity +import com.kom.skyfly.data.mapper.toSearchDestinationHistoryList import com.kom.skyfly.data.model.home.search.Airport +import com.kom.skyfly.data.model.home.search_history.SearchDestinationHistory +import com.kom.skyfly.data.source.local.database.entity.SearchDestinationHistoryEntity import com.kom.skyfly.utils.ResultWrapper +import com.kom.skyfly.utils.proceedFlow import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart class AirportRepositoryImpl( private val dataSource: HomeDataSource, + private val searchHistoryDataSource: SearchHistoryDataSource, ) : AirportRepository { - override fun getAllAirportData(): Flow>> { + override fun getAllAirportData(city: String?): Flow>> { return flow { emit(ResultWrapper.Loading()) - val result = dataSource.getAllAirports().data.toAirports() + val result = dataSource.getAllAirports(city = city).data.toAirports() // delay(500) emit(ResultWrapper.Success(result)) } } + + override fun createSearchDestinationHistory(searchDestinationHistory: String): Flow> { + return proceedFlow { + val affectedRows = + searchHistoryDataSource.insertSearchDestinationHistory( + SearchDestinationHistoryEntity( + searchDestinationHistory = searchDestinationHistory, + ), + ) + affectedRows > 0 + } + } + + override fun deleteSearchDestinationHistory(item: SearchDestinationHistory): Flow> { + return proceedFlow { searchHistoryDataSource.deleteSearchDestinationHistory(item.toSearchDestinationHistoryEntity()) > 0 } + } + + override fun deleteAllSearchDestinationHistory(): Flow> { + return proceedFlow { searchHistoryDataSource.deleteAllSearchDestinationHistory() } + } + + override fun getUserSearchDestinationHistory(): Flow>> { + return searchHistoryDataSource.getAllSearchDestinationHistory() + .map { searchHistoryEntities -> + val searchHistoryList = searchHistoryEntities.toSearchDestinationHistoryList() // Handle nullable case + if (searchHistoryList != null) { + ResultWrapper.Success(searchHistoryList) + } else { + ResultWrapper.Empty() + } + }.onStart { + }.catch { e -> + } + } } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/home/destination_favourite/DestinationFavouriteRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/home/destination_favourite/DestinationFavouriteRepository.kt new file mode 100644 index 0000000..f88496e --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/repository/home/destination_favourite/DestinationFavouriteRepository.kt @@ -0,0 +1,30 @@ +package com.kom.skyfly.data.repository.home.destination_favourite + +import com.kom.skyfly.data.datasource.home.HomeDataSource +import com.kom.skyfly.data.mapper.home.toDestinationFavourites +import com.kom.skyfly.data.model.home.destination_favourite.DestinationFavourite +import com.kom.skyfly.utils.ResultWrapper +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** +Written by Komang Yuda Saputra +Github : https://github.com/YudaSaputraa + **/ +interface DestinationFavouriteRepository { + fun getAllDestinationFavourite(): Flow>> +} + +class DestinationFavouriteRepositoryImpl(private val destinationFavouriteDataSource: HomeDataSource) : + DestinationFavouriteRepository { + override fun getAllDestinationFavourite(): Flow>> { + return flow { + emit(ResultWrapper.Loading()) + delay(1000) + val result = + destinationFavouriteDataSource.getDestinationFavourites().data.toDestinationFavourites() + emit(ResultWrapper.Success(result)) + } + } +} diff --git a/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepository.kt index b6c9000..999a5f6 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepository.kt @@ -2,6 +2,7 @@ package com.kom.skyfly.data.repository.home.flight_ticket import com.kom.skyfly.data.model.home.flight.FlightTicket import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket +import com.kom.skyfly.data.model.home.seat_class.SeatClassHome import com.kom.skyfly.utils.ResultWrapper import kotlinx.coroutines.flow.Flow @@ -12,11 +13,34 @@ interface FlightTicketRepository { departureAirport: String, arrivalAirport: String, departureDate: String, - seatClass: String, + seatClass: String?, + limit: Int? = 20, + returnDate: String? = null, + arrivalDate: String? = null, + adult: Int? = 1, + children: Int? = 0, + baby: Int? = 0, + ): Flow>> + + fun getReturnTicket( + search: String? = null, + page: Int? = null, + departureAirport: String? = null, + arrivalAirport: String? = null, + departureDate: String? = null, + seatClass: String? = null, + limit: Int? = 10000, + returnDate: String? = null, + arrivalDate: String? = null, + adult: Int? = 1, + children: Int? = 0, + baby: Int? = 0, ): Flow>> fun getDetailTicket( id: String, - seatClass: String, + seatClass: String?, ): Flow> + + fun getSeatClassTicket(): List } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepositoryImpl.kt b/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepositoryImpl.kt index 73c55a5..4afc531 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepositoryImpl.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/home/flight_ticket/FlightTicketRepositoryImpl.kt @@ -5,6 +5,7 @@ import com.kom.skyfly.data.mapper.home.toFlightDetailTicket import com.kom.skyfly.data.mapper.home.toFlightTickets import com.kom.skyfly.data.model.home.flight.FlightTicket import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket +import com.kom.skyfly.data.model.home.seat_class.SeatClassHome import com.kom.skyfly.utils.ResultWrapper import com.kom.skyfly.utils.proceedFlow import kotlinx.coroutines.flow.Flow @@ -18,22 +19,65 @@ class FlightTicketRepositoryImpl( departureAirport: String, arrivalAirport: String, departureDate: String, - seatClass: String, + seatClass: String?, + limit: Int?, + returnDate: String?, + arrivalDate: String?, + adult: Int?, + children: Int?, + baby: Int?, ): Flow>> { return proceedFlow { dataSource.getAllFlight( page = page, departureAirport = departureAirport, + limit = limit, arrivalAirport = arrivalAirport, departureDate = departureDate, seatClass = seatClass, + returnDate = returnDate, + adult = adult, + baby = baby, + children = children, ).data.toFlightTickets() } } + override fun getReturnTicket( + search: String?, + page: Int?, + departureAirport: String?, + arrivalAirport: String?, + departureDate: String?, + seatClass: String?, + limit: Int?, + returnDate: String?, + arrivalDate: String?, + adult: Int?, + children: Int?, + baby: Int?, + ): Flow>> { + return proceedFlow { + dataSource.getAllFlight( + page = page ?: 0, + departureAirport = departureAirport.orEmpty(), + limit = limit, + arrivalAirport = arrivalAirport!!, + departureDate = departureDate.orEmpty(), + seatClass = seatClass, + returnDate = returnDate, + adult = adult, + baby = baby, + children = children, + ).returnFlight.toFlightTickets() + } + } + + override fun getSeatClassTicket(): List = dataSource.getSeatClassData() + override fun getDetailTicket( id: String, - seatClass: String, + seatClass: String?, ): Flow> { return proceedFlow { dataSource.getDetailFlight( diff --git a/app/src/main/java/com/kom/skyfly/data/repository/notification/NotificationRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/notification/NotificationRepository.kt index 993c3dc..67f0a8c 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/notification/NotificationRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/notification/NotificationRepository.kt @@ -4,9 +4,8 @@ import com.kom.skyfly.data.datasource.notification.NotificationDataSource import com.kom.skyfly.data.mapper.toNotifications import com.kom.skyfly.data.model.notification.Notification import com.kom.skyfly.utils.ResultWrapper -import kotlinx.coroutines.delay +import com.kom.skyfly.utils.proceedFlow import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow /** Written by Komang Yuda Saputra @@ -19,11 +18,8 @@ interface NotificationRepository { class NotificationRepositoryImpl(private val notificationDataSource: NotificationDataSource) : NotificationRepository { override fun getAllNotification(): Flow>> { - return flow { - emit(ResultWrapper.Loading()) - delay(1000) - val result = notificationDataSource.getAllNotification().data.toNotifications() - emit(ResultWrapper.Success(result)) + return proceedFlow { + notificationDataSource.getAllNotification().data.toNotifications() } } } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/profiles/ProfileRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/profiles/ProfileRepository.kt index fe36b96..f3ee204 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/profiles/ProfileRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/profiles/ProfileRepository.kt @@ -18,6 +18,7 @@ interface ProfileRepository { fun updateProfile( name: String?, phoneNumber: String?, + familyName: String?, password: String?, confirmPassword: String?, ): Flow> @@ -31,9 +32,10 @@ class ProfileRepositoryImpl(private val dataSource: ProfileDataSource) : Profile override fun updateProfile( name: String?, phoneNumber: String?, + familyName: String?, password: String?, confirmPassword: String?, ): Flow> { - return proceedFlow { dataSource.updateUserProfile(name, phoneNumber, password, confirmPassword) } + return proceedFlow { dataSource.updateUserProfile(name, phoneNumber, familyName, password, confirmPassword) } } } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/searchhistory/SearchHistoryRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/searchhistory/SearchHistoryRepository.kt index 0039eb1..3c02ad0 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/searchhistory/SearchHistoryRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/searchhistory/SearchHistoryRepository.kt @@ -58,14 +58,17 @@ class SearchHistoryRepositoryImpl(private val searchHistoryDataSource: SearchHis override fun getUserSearchHistory(): Flow>> { return searchHistoryDataSource.getAllSearchHistory() .map { searchHistoryEntities -> - val searchHistoryList = searchHistoryEntities.toSearchHistoryList() // Handle nullable case + val searchHistoryList = + searchHistoryEntities.toSearchHistoryList() if (searchHistoryList != null) { ResultWrapper.Success(searchHistoryList) } else { ResultWrapper.Empty() } - }.onStart { }.catch { e -> + emit(ResultWrapper.Error(Exception(e))) + }.onStart { + emit(ResultWrapper.Loading()) } } } diff --git a/app/src/main/java/com/kom/skyfly/data/repository/transaction/TransactionRepository.kt b/app/src/main/java/com/kom/skyfly/data/repository/transaction/TransactionRepository.kt index e3ed711..eb2f9cb 100644 --- a/app/src/main/java/com/kom/skyfly/data/repository/transaction/TransactionRepository.kt +++ b/app/src/main/java/com/kom/skyfly/data/repository/transaction/TransactionRepository.kt @@ -1,8 +1,12 @@ package com.kom.skyfly.data.repository.transaction import com.kom.skyfly.data.datasource.transaction.TransactionDataSource +import com.kom.skyfly.data.mapper.toCancelTransaction +import com.kom.skyfly.data.mapper.toItemsPaymentStatusDomain import com.kom.skyfly.data.mapper.toTransactionDetailResponse +import com.kom.skyfly.data.model.transaction.cancel.ItemsCancelTransactionModel import com.kom.skyfly.data.model.transaction.detail.TransactionDetailResponses +import com.kom.skyfly.data.model.transaction.paymentstatus.ItemsPaymentStatus import com.kom.skyfly.data.source.network.model.transaction.request.TransactionRequest import com.kom.skyfly.data.source.network.model.transaction.response.TransactionResponse import com.kom.skyfly.utils.ResultWrapper @@ -23,6 +27,10 @@ interface TransactionRepository { ): Flow> fun getTransactionById(id: String): Flow> + + fun getPaymentStatus(id: String): Flow> + + fun cancelTransaction(id: String): Flow> } class TransactionRepositoryImpl(private val datasource: TransactionDataSource) : @@ -47,7 +55,20 @@ class TransactionRepositoryImpl(private val datasource: TransactionDataSource) : override fun getTransactionById(id: String): Flow> { return proceedFlow { - datasource.getTransactionById(id).toTransactionDetailResponse() + datasource.getTransactionById(id) + .toTransactionDetailResponse() + } + } + + override fun getPaymentStatus(id: String): Flow> { + return proceedFlow { + datasource.getPaymentStatus(id).data.toItemsPaymentStatusDomain() + } + } + + override fun cancelTransaction(id: String): Flow> { + return proceedFlow { + datasource.cancelTransaction(id).data.toCancelTransaction() } } } diff --git a/app/src/main/java/com/kom/skyfly/data/source/local/database/AppDatabase.kt b/app/src/main/java/com/kom/skyfly/data/source/local/database/AppDatabase.kt index 32a1489..6d14e8d 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/local/database/AppDatabase.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/local/database/AppDatabase.kt @@ -14,7 +14,7 @@ Github : https://github.com/YudaSaputraa **/ @Database( entities = [SearchHistoryEntity::class, SearchDestinationHistoryEntity::class], - version = 2, + version = 3, exportSchema = true, ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/kom/skyfly/data/source/local/database/dao/SearchHistoryDao.kt b/app/src/main/java/com/kom/skyfly/data/source/local/database/dao/SearchHistoryDao.kt index bbcce07..a16880e 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/local/database/dao/SearchHistoryDao.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/local/database/dao/SearchHistoryDao.kt @@ -25,12 +25,24 @@ interface SearchHistoryDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertSearchHistory(searchHistory: SearchHistoryEntity): Long + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Long + @Update suspend fun updateSearchHistory(searchHistory: SearchHistoryEntity): Int + @Update + suspend fun updateSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Int + @Delete suspend fun deleteSearchHistory(searchHistory: SearchHistoryEntity): Int + @Delete + suspend fun deleteSearchDestinationHistory(searchDestinationHistory: SearchDestinationHistoryEntity): Int + @Query("DELETE FROM search_history") suspend fun deleteAll() + + @Query("DELETE FROM search_destination_history") + suspend fun deleteAllRecentSearchDestination() } diff --git a/app/src/main/java/com/kom/skyfly/data/source/local/database/entity/SearchDestinationHistoryEntity.kt b/app/src/main/java/com/kom/skyfly/data/source/local/database/entity/SearchDestinationHistoryEntity.kt index 4ab22fe..32e7034 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/local/database/entity/SearchDestinationHistoryEntity.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/local/database/entity/SearchDestinationHistoryEntity.kt @@ -9,5 +9,5 @@ data class SearchDestinationHistoryEntity( @PrimaryKey(autoGenerate = true) var id: Int? = null, @ColumnInfo(name = "search_history") - var searchHistory: String, + var searchDestinationHistory: String, ) diff --git a/app/src/main/java/com/kom/skyfly/data/source/local/pref/UserPreference.kt b/app/src/main/java/com/kom/skyfly/data/source/local/pref/UserPreference.kt index 15152a9..5214948 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/local/pref/UserPreference.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/local/pref/UserPreference.kt @@ -1,6 +1,7 @@ package com.kom.skyfly.data.source.local.pref import android.content.SharedPreferences +import com.kom.skyfly.utils.SharedPreferenceUtils.get import com.kom.skyfly.utils.SharedPreferenceUtils.set /** @@ -42,5 +43,6 @@ class UserPreferenceImpl(private val pref: SharedPreferences) : UserPreference { const val PREF_NAME = "skyfly-pref" const val KEY_ON_BOARDING_SHOW = "KEY_ON_BOARDING_SHOW" const val KEY_TOKEN = "KEY_TOKEN" + const val KEY_PAYMENT_URL = "KEY_PAYMENT_URL" } } diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Airline.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Airline.kt new file mode 100644 index 0000000..cb8b282 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Airline.kt @@ -0,0 +1,18 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Airline( + @SerializedName("code") + val code: String?, + @SerializedName("id") + val id: String?, + @SerializedName("image") + val image: String?, + @SerializedName("name") + val name: String?, + @SerializedName("terminal") + val terminal: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Arrival.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Arrival.kt new file mode 100644 index 0000000..abc3d47 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Arrival.kt @@ -0,0 +1,12 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Arrival( + @SerializedName("date") + val date: String?, + @SerializedName("time") + val time: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Booking.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Booking.kt new file mode 100644 index 0000000..80762f4 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Booking.kt @@ -0,0 +1,14 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Booking( + @SerializedName("code") + val code: String?, + @SerializedName("date") + val date: String?, + @SerializedName("time") + val time: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Departure.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Departure.kt new file mode 100644 index 0000000..fcfda26 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Departure.kt @@ -0,0 +1,12 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Departure( + @SerializedName("date") + val date: String?, + @SerializedName("time") + val time: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/DepartureAirport.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/DepartureAirport.kt new file mode 100644 index 0000000..f5a6bde --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/DepartureAirport.kt @@ -0,0 +1,22 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class DepartureAirport( + @SerializedName("city") + val city: String?, + @SerializedName("code") + val code: String?, + @SerializedName("continent") + val continent: String?, + @SerializedName("country") + val country: String?, + @SerializedName("id") + val id: String?, + @SerializedName("image") + val image: String?, + @SerializedName("name") + val name: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/DestinationAirport.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/DestinationAirport.kt new file mode 100644 index 0000000..af8f588 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/DestinationAirport.kt @@ -0,0 +1,22 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class DestinationAirport( + @SerializedName("city") + val city: String?, + @SerializedName("code") + val code: String?, + @SerializedName("continent") + val continent: String?, + @SerializedName("country") + val country: String?, + @SerializedName("id") + val id: String?, + @SerializedName("image") + val image: String?, + @SerializedName("name") + val name: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Flight.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Flight.kt new file mode 100644 index 0000000..96df796 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Flight.kt @@ -0,0 +1,26 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Flight( + @SerializedName("airline") + val airline: Airline?, + @SerializedName("arrival") + val arrival: Arrival?, + @SerializedName("code") + val code: String?, + @SerializedName("departure") + val departure: Departure?, + @SerializedName("departureAirport") + val departureAirport: DepartureAirport?, + @SerializedName("destinationAirport") + val destinationAirport: DestinationAirport?, + @SerializedName("flightDuration") + val flightDuration: String?, + @SerializedName("flightPrice") + val flightPrice: Int?, + @SerializedName("id") + val id: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/HistoryResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/HistoryResponse.kt new file mode 100644 index 0000000..2517ba1 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/HistoryResponse.kt @@ -0,0 +1,16 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class HistoryResponse( + @SerializedName("data") + val data: List?, + @SerializedName("message") + val message: String?, + @SerializedName("status") + val status: Boolean?, + @SerializedName("totalItems") + val totalItems: Int?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/ItemsHistoryResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/ItemsHistoryResponse.kt new file mode 100644 index 0000000..c723bd9 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/ItemsHistoryResponse.kt @@ -0,0 +1,12 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class ItemsHistoryResponse( + @SerializedName("date") + val date: String?, + @SerializedName("transactions") + val transactions: List?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Pagination.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Pagination.kt new file mode 100644 index 0000000..730b82b --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Pagination.kt @@ -0,0 +1,18 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Pagination( + @SerializedName("currentPage") + val currentPage: Int?, + @SerializedName("nextPage") + val nextPage: Int?, + @SerializedName("pageItems") + val pageItems: Int?, + @SerializedName("prevPage") + val prevPage: Int?, + @SerializedName("totalPage") + val totalPage: Int?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Seat.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Seat.kt new file mode 100644 index 0000000..b5be600 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Seat.kt @@ -0,0 +1,20 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Seat( + @SerializedName("flightId") + val flightId: String?, + @SerializedName("id") + val id: String?, + @SerializedName("seatNumber") + val seatNumber: String?, + @SerializedName("seatPrice") + val seatPrice: Int?, + @SerializedName("status") + val status: String?, + @SerializedName("type") + val type: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Transaction.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Transaction.kt new file mode 100644 index 0000000..8cc3a97 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/Transaction.kt @@ -0,0 +1,24 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class Transaction( + @SerializedName("booking") + val booking: Booking?, + @SerializedName("id") + val id: String?, + @SerializedName("orderId") + val orderId: String?, + @SerializedName("status") + val status: String?, + @SerializedName("tax") + val tax: Int?, + @SerializedName("totalPrice") + val totalPrice: Int?, + @SerializedName("Transaction_Detail") + val transactionDetail: List?, + @SerializedName("userId") + val userId: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/history/TransactionDetail.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/TransactionDetail.kt new file mode 100644 index 0000000..f68cded --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/history/TransactionDetail.kt @@ -0,0 +1,34 @@ +package com.kom.skyfly.data.source.network.model.history + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class TransactionDetail( + @SerializedName("citizenship") + val citizenship: String?, + @SerializedName("dob") + val dob: String?, + @SerializedName("familyName") + val familyName: String?, + @SerializedName("flight") + val flight: Flight?, + @SerializedName("id") + val id: String?, + @SerializedName("issuingCountry") + val issuingCountry: String?, + @SerializedName("name") + val name: String?, + @SerializedName("passengerCategory") + val passengerCategory: String?, + @SerializedName("passport") + val passport: String?, + @SerializedName("seat") + val seat: Seat?, + @SerializedName("totalPrice") + val totalPrice: Int?, + @SerializedName("transactionId") + val transactionId: String?, + @SerializedName("validityPeriod") + val validityPeriod: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/ArrivalDestination.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/ArrivalDestination.kt new file mode 100644 index 0000000..1c2d5c4 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/ArrivalDestination.kt @@ -0,0 +1,12 @@ +package com.kom.skyfly.data.source.network.model.home.favourite_destination + +import com.google.gson.annotations.SerializedName + +data class ArrivalDestination( + @SerializedName("arrivalDate") + val arrivalDate: String, + @SerializedName("arrivalCity") + val arrivalCity: String, + @SerializedName("image") + val image: String, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FavouriteDestinationData.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FavouriteDestinationData.kt new file mode 100644 index 0000000..df738b4 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FavouriteDestinationData.kt @@ -0,0 +1,14 @@ +package com.kom.skyfly.data.source.network.model.home.favourite_destination + +import com.google.gson.annotations.SerializedName + +data class FavouriteDestinationData( + @SerializedName("flightId") + val flightId: String, + @SerializedName("from") + val sourceDestination: SourceDestination, + @SerializedName("to") + val arrivalDestination: ArrivalDestination, + @SerializedName("plane") + val plane: Plane, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FavouriteDestinationResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FavouriteDestinationResponse.kt new file mode 100644 index 0000000..579812b --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FavouriteDestinationResponse.kt @@ -0,0 +1,14 @@ +package com.kom.skyfly.data.source.network.model.home.favourite_destination + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class FavouriteDestinationResponse( + @SerializedName("status") + val status: Boolean, + @SerializedName("message") + val message: String, + @SerializedName("data") + val data: List, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FlightDetailResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FlightDetailResponse.kt new file mode 100644 index 0000000..d27b29b --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/FlightDetailResponse.kt @@ -0,0 +1,10 @@ +package com.kom.skyfly.data.source.network.model.home.favourite_destination + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class FlightDetailResponse( + @SerializedName("flightDetails") + val flightDetails: FavouriteDestinationData, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/Plane.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/Plane.kt new file mode 100644 index 0000000..a1a2ae9 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/Plane.kt @@ -0,0 +1,10 @@ +package com.kom.skyfly.data.source.network.model.home.favourite_destination + +import com.google.gson.annotations.SerializedName + +data class Plane( + @SerializedName("airline") + val airline: String, + @SerializedName("price") + val price: Int, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/SourceDestination.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/SourceDestination.kt new file mode 100644 index 0000000..bdc52cf --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/favourite_destination/SourceDestination.kt @@ -0,0 +1,10 @@ +package com.kom.skyfly.data.source.network.model.home.favourite_destination + +import com.google.gson.annotations.SerializedName + +data class SourceDestination( + @SerializedName("departureDate") + val departureDate: String, + @SerializedName("departureCity") + val departureCity: String, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/FlightResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/FlightResponse.kt index 5f31c17..1b9281f 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/FlightResponse.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/FlightResponse.kt @@ -19,4 +19,6 @@ data class FlightResponse( val priceRange: PriceRange, @SerializedName("data") val data: List, + @SerializedName("returnFlights") + val returnFlight: List, ) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/PriceRange.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/PriceRange.kt index ce7fb67..72046ae 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/PriceRange.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/home/flight/PriceRange.kt @@ -10,4 +10,5 @@ data class PriceRange( val economyClass: Price, @SerializedName("BUSINESS") val businessClass: Price, + val name: String? = "Business", ) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/ItemsPaymentStatusResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/ItemsPaymentStatusResponse.kt new file mode 100644 index 0000000..d710f3a --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/ItemsPaymentStatusResponse.kt @@ -0,0 +1,32 @@ +package com.kom.skyfly.data.source.network.model.paymentstatus + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class ItemsPaymentStatusResponse( + @SerializedName("currency") + val currency: String?, + @SerializedName("expiry_time") + val expiryTime: String?, + @SerializedName("gross_amount") + val grossAmount: String?, + @SerializedName("merchant_id") + val merchantId: String?, + @SerializedName("order_id") + val orderId: String?, + @SerializedName("payment_status") + val paymentStatus: String?, + @SerializedName("payment_type") + val paymentType: String?, + @SerializedName("signature_key") + val signatureKey: String?, + @SerializedName("transaction_id") + val transactionId: String?, + @SerializedName("transaction_status") + val transactionStatus: String?, + @SerializedName("transaction_time") + val transactionTime: String?, + @SerializedName("va_numbers") + val vaNumbers: List?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/PaymentStatusResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/PaymentStatusResponse.kt new file mode 100644 index 0000000..3d55ed2 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/PaymentStatusResponse.kt @@ -0,0 +1,14 @@ +package com.kom.skyfly.data.source.network.model.paymentstatus + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class PaymentStatusResponse( + @SerializedName("data") + val data: ItemsPaymentStatusResponse?, + @SerializedName("message") + val message: String?, + @SerializedName("status") + val status: Boolean?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/VaNumber.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/VaNumber.kt new file mode 100644 index 0000000..2b280d2 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/paymentstatus/VaNumber.kt @@ -0,0 +1,12 @@ +package com.kom.skyfly.data.source.network.model.paymentstatus + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class VaNumber( + @SerializedName("bank") + val bank: String?, + @SerializedName("va_number") + val vaNumber: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/resendotp/ResendOtpSmsRequest.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/resendotp/ResendOtpSmsRequest.kt new file mode 100644 index 0000000..b160234 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/resendotp/ResendOtpSmsRequest.kt @@ -0,0 +1,11 @@ +package com.kom.skyfly.data.source.network.model.resendotp + +import com.google.gson.annotations.SerializedName + +/** +Written by Komang Yuda Saputra +Github : https://github.com/YudaSaputraa + **/ +data class ResendOtpSmsRequest( + @SerializedName("phoneNumber") val phoneNumber: String, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/transaction/cancel/CancelTransactionResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/transaction/cancel/CancelTransactionResponse.kt new file mode 100644 index 0000000..e7a22b0 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/transaction/cancel/CancelTransactionResponse.kt @@ -0,0 +1,14 @@ +package com.kom.skyfly.data.source.network.model.transaction.cancel + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class CancelTransactionResponse( + @SerializedName("data") + val data: ItemsCancelTransactionResponse?, + @SerializedName("message") + val message: String?, + @SerializedName("status") + val status: Boolean?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/transaction/cancel/ItemsCancelTransactionResponse.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/transaction/cancel/ItemsCancelTransactionResponse.kt new file mode 100644 index 0000000..6917d47 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/transaction/cancel/ItemsCancelTransactionResponse.kt @@ -0,0 +1,26 @@ +package com.kom.skyfly.data.source.network.model.transaction.cancel + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class ItemsCancelTransactionResponse( + @SerializedName("currency") + val currency: String?, + @SerializedName("gross_amount") + val grossAmount: String?, + @SerializedName("merchant_id") + val merchantId: String?, + @SerializedName("order_id") + val orderId: String?, + @SerializedName("payment_status") + val paymentStatus: String?, + @SerializedName("payment_type") + val paymentType: String?, + @SerializedName("transaction_id") + val transactionId: String?, + @SerializedName("transaction_status") + val transactionStatus: String?, + @SerializedName("transaction_time") + val transactionTime: String?, +) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/model/userprofile/updateprofile/UpdateProfileRequest.kt b/app/src/main/java/com/kom/skyfly/data/source/network/model/userprofile/updateprofile/UpdateProfileRequest.kt index 05b3509..1332223 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/network/model/userprofile/updateprofile/UpdateProfileRequest.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/network/model/userprofile/updateprofile/UpdateProfileRequest.kt @@ -9,6 +9,7 @@ Github : https://github.com/YudaSaputraa data class UpdateProfileRequest( @SerializedName("name") val name: String?, @SerializedName("phoneNumber") val phoneNumber: String?, + @SerializedName("familyName") val familyName: String?, @SerializedName("password") val password: String?, @SerializedName("confirmPassword") val confirmPassword: String?, ) diff --git a/app/src/main/java/com/kom/skyfly/data/source/network/services/SkyFlyApiService.kt b/app/src/main/java/com/kom/skyfly/data/source/network/services/SkyFlyApiService.kt index 8d90f5e..1798404 100644 --- a/app/src/main/java/com/kom/skyfly/data/source/network/services/SkyFlyApiService.kt +++ b/app/src/main/java/com/kom/skyfly/data/source/network/services/SkyFlyApiService.kt @@ -7,15 +7,20 @@ import com.kom.skyfly.data.source.local.pref.UserPreference import com.kom.skyfly.data.source.network.model.flightseat.FlightSeatResponse import com.kom.skyfly.data.source.network.model.forgetpassword.ForgetPasswordRequest import com.kom.skyfly.data.source.network.model.forgetpassword.ForgetPasswordResponse +import com.kom.skyfly.data.source.network.model.history.HistoryResponse import com.kom.skyfly.data.source.network.model.home.airport.AirportResponse +import com.kom.skyfly.data.source.network.model.home.favourite_destination.FavouriteDestinationResponse import com.kom.skyfly.data.source.network.model.home.flight.FlightResponse import com.kom.skyfly.data.source.network.model.home.flight_detail.FlightDetailResponse import com.kom.skyfly.data.source.network.model.login.LoginRequest import com.kom.skyfly.data.source.network.model.login.LoginResponse import com.kom.skyfly.data.source.network.model.notification.NotificationResponse +import com.kom.skyfly.data.source.network.model.paymentstatus.PaymentStatusResponse import com.kom.skyfly.data.source.network.model.register.RegisterRequest import com.kom.skyfly.data.source.network.model.register.RegisterResponse import com.kom.skyfly.data.source.network.model.resendotp.ResendOtpResponse +import com.kom.skyfly.data.source.network.model.resendotp.ResendOtpSmsRequest +import com.kom.skyfly.data.source.network.model.transaction.cancel.CancelTransactionResponse import com.kom.skyfly.data.source.network.model.transaction.detail.TransactionDetailResponse import com.kom.skyfly.data.source.network.model.transaction.request.TransactionRequest import com.kom.skyfly.data.source.network.model.transaction.response.TransactionResponse @@ -72,22 +77,29 @@ interface SkyFlyApiService { @GET("api/v1/flights/") suspend fun getAllFlights( @Query("search") search: String? = null, - @Query("page") page: Int, - @Query("departureAirport") departureAirport: String, - @Query("arrivalAirport") arrivalAirport: String, - @Query("departureDate") departureDate: String, + @Query("page") page: Int?, + @Query("limit") limit: Int? = 10000, + @Query("departureAirport") departureAirport: String?, + @Query("arrivalAirport") arrivalAirport: String?, + @Query("departureDate") departureDate: String?, + @Query("returnDate") returnDate: String?, @Query("arrivalDate") arrivalDate: String? = null, - @Query("seatClass") seatClass: String, + @Query("seatClass") seatClass: String?, + @Query("adult") adult: Int? = 0, + @Query("children") children: Int? = 0, + @Query("baby") baby: Int? = 0, + @Query("sort") sort: String? = null, ): FlightResponse @GET("api/v1/flights/{id}") suspend fun getDetailFlightById( @Path("id") id: String, - @Query("seatClass") seatClass: String, + @Query("seatClass") seatClass: String?, ): FlightDetailResponse @GET("api/v1/airports/") suspend fun getAllAirports( + @Query("city") city: String?, @Query("showall") showAll: Boolean = true, ): AirportResponse @@ -97,6 +109,9 @@ interface SkyFlyApiService { @Query("limit") limit: Int? = 5000, ): FlightSeatResponse + @GET("api/v1/flights/favorite-destination") + suspend fun getDestinationFavourite(): FavouriteDestinationResponse + @GET("api/v1/auth/me") suspend fun getUserProfile(): UserProfileResponse @@ -124,6 +139,30 @@ interface SkyFlyApiService { @Path("id") id: String, ): TransactionDetailResponse + @POST("api/v1/auth/verified/resendSMS-otp") + suspend fun resendOtpSms( + @Query("token") token: String, + @Body resendOtpRequest: ResendOtpSmsRequest, + ): ResendOtpResponse + + @GET("api/v1/transactions") + suspend fun getAllTransactionHistory( + @Query("limit") limit: Int?, + @Query("startDate") startDate: String?, + @Query("endDate") endDate: String?, + @Query("flightCode") flightCode: String?, + ): HistoryResponse + + @GET("api/v1/transactions/status/{id}") + suspend fun getPaymentStatus( + @Path("id") id: String, + ): PaymentStatusResponse + + @POST("api/v1/transactions/cancel/{id}") + suspend fun cancelTransaction( + @Path("id") id: String, + ): CancelTransactionResponse + companion object { @JvmStatic operator fun invoke( @@ -141,7 +180,7 @@ interface SkyFlyApiService { .addInterceptor { chain -> val original: Request = chain.request() val token = userPreference.getUserToken() -// val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImUxOTM5OGQ4LTI3YTUtNDRkMi1iMzYxLTVjNDI2NjU0YjBiOCIsIm5hbWUiOiJrb21hbmd5dWRhIiwiZW1haWwiOiJ5dWRhc2FwdXRyYTA4MkBnbWFpbC5jb20iLCJwaG9uZU51bWJlciI6IjYyODc0Njc0NjQ3ODciLCJpYXQiOjE3MTgwMzQzMzksImV4cCI6MTcxODEyMDczOX0.r_vTbQwhr3NMZdGzfGyveF6rE-E1LOCC0BGv45dhNAw" + // val token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImUxOTM5OGQ4LTI3YTUtNDRkMi1iMzYxLTVjNDI2NjU0YjBiOCIsIm5hbWUiOiJrb21hbmd5dWRhIiwiZW1haWwiOiJ5dWRhc2FwdXRyYTA4MkBnbWFpbC5jb20iLCJwaG9uZU51bWJlciI6IjYyODc0Njc0NjQ3ODciLCJpYXQiOjE3MTgwMzQzMzksImV4cCI6MTcxODEyMDczOX0.r_vTbQwhr3NMZdGzfGyveF6rE-E1LOCC0BGv45dhNAw" val requestBuilder: Request.Builder = original.newBuilder() .addHeader("accept", "application/json") diff --git a/app/src/main/java/com/kom/skyfly/di/AppModules.kt b/app/src/main/java/com/kom/skyfly/di/AppModules.kt index 9d1f0ee..d322e9e 100644 --- a/app/src/main/java/com/kom/skyfly/di/AppModules.kt +++ b/app/src/main/java/com/kom/skyfly/di/AppModules.kt @@ -1,13 +1,10 @@ package com.kom.skyfly.di import android.content.SharedPreferences -import android.os.Bundle import com.chuckerteam.chucker.api.ChuckerInterceptor import com.kom.skyfly.core.BaseViewModel import com.kom.skyfly.data.datasource.auth.AuthDataSource import com.kom.skyfly.data.datasource.auth.AuthDataSourceImpl -import com.kom.skyfly.data.datasource.destinationfavorite.DestinationFavoriteDataSource -import com.kom.skyfly.data.datasource.destinationfavorite.DestinationFavoriteDataSourceImpl import com.kom.skyfly.data.datasource.flightseat.FlightSeatDataSource import com.kom.skyfly.data.datasource.flightseat.FlightSeatDataSourceImpl import com.kom.skyfly.data.datasource.history.HistoryDataSource @@ -26,14 +23,14 @@ import com.kom.skyfly.data.datasource.userpreference.UserPrefDataSource import com.kom.skyfly.data.datasource.userpreference.UserPrefDataSourceImpl import com.kom.skyfly.data.repository.auth.AuthRepository import com.kom.skyfly.data.repository.auth.AuthRepositoryImpl -import com.kom.skyfly.data.repository.destinationfavorite.DestinationFavoriteRepository -import com.kom.skyfly.data.repository.destinationfavorite.DestinationFavoriteRepositoryImpl import com.kom.skyfly.data.repository.flightseat.FlightSeatRepository import com.kom.skyfly.data.repository.flightseat.FlightSeatRepositoryImpl import com.kom.skyfly.data.repository.history.HistoryRepository import com.kom.skyfly.data.repository.history.HistoryRepositoryImpl import com.kom.skyfly.data.repository.home.airport.AirportRepository import com.kom.skyfly.data.repository.home.airport.AirportRepositoryImpl +import com.kom.skyfly.data.repository.home.destination_favourite.DestinationFavouriteRepository +import com.kom.skyfly.data.repository.home.destination_favourite.DestinationFavouriteRepositoryImpl import com.kom.skyfly.data.repository.home.flight_ticket.FlightTicketRepository import com.kom.skyfly.data.repository.home.flight_ticket.FlightTicketRepositoryImpl import com.kom.skyfly.data.repository.notification.NotificationRepository @@ -57,7 +54,6 @@ import com.kom.skyfly.presentation.checkout.bookersbiodata.BookersBiodataViewMod import com.kom.skyfly.presentation.checkout.checkoutticket.CheckoutTicketViewModel import com.kom.skyfly.presentation.checkout.chooseseat.ChooseSeatViewModel import com.kom.skyfly.presentation.checkout.flightdetail.FlightDetailViewModel -import com.kom.skyfly.presentation.checkout.passengerbiodata.PassengerBiodataViewModel import com.kom.skyfly.presentation.forgetpassword.ForgetPasswordViewModel import com.kom.skyfly.presentation.history.HistoryViewModel import com.kom.skyfly.presentation.history.flightdetailhistory.FlightDetailHistoryViewModel @@ -66,6 +62,7 @@ import com.kom.skyfly.presentation.home.HomeViewModel import com.kom.skyfly.presentation.home.detail_home.DetailHomeViewModel import com.kom.skyfly.presentation.home.search.SearchViewModel import com.kom.skyfly.presentation.home.search_result.SearchResultViewModel +import com.kom.skyfly.presentation.home.seatclass.SeatClassViewModel import com.kom.skyfly.presentation.login.LoginViewModel import com.kom.skyfly.presentation.main.MainViewModel import com.kom.skyfly.presentation.notification.NotificationViewModel @@ -74,7 +71,6 @@ import com.kom.skyfly.presentation.register.RegisterViewModel import com.kom.skyfly.presentation.verifyotp.VerifyOtpViewModel import com.kom.skyfly.utils.SharedPreferenceUtils import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModelOf import org.koin.dsl.module @@ -93,13 +89,12 @@ object AppModules { module { single { AuthDataSourceImpl(get()) } single { UserPrefDataSourceImpl(get()) } - single { HistoryDataSourceImpl() } + single { HistoryDataSourceImpl(get()) } single { NotificationDataSourceImpl(get()) } single { ProfileDataSourceImpl(get()) } single { SearchHistoryDataSourceImpl(get()) } single { HomeDataSourceImpl(get()) } single { FlightSeatDataSourceImpl(get()) } - single { DestinationFavoriteDataSourceImpl() } single { TransactionDataSourceImpl(get()) } } @@ -124,9 +119,9 @@ object AppModules { single { HistoryRepositoryImpl(get()) } single { ProfileRepositoryImpl(get()) } single { SearchHistoryRepositoryImpl(get()) } - single { AirportRepositoryImpl(get()) } + single { AirportRepositoryImpl(get(), get()) } single { FlightSeatRepositoryImpl(get()) } - single { DestinationFavoriteRepositoryImpl(get()) } + single { DestinationFavouriteRepositoryImpl(get()) } single { FlightTicketRepositoryImpl(get()) } single { TransactionRepositoryImpl(get()) } } @@ -145,20 +140,15 @@ object AppModules { viewModelOf(::AccountViewModel) viewModelOf(::NotificationViewModel) viewModelOf(::HistoryViewModel) + viewModelOf(::SeatClassViewModel) viewModelOf(::DetailHomeViewModel) viewModelOf(::SearchFlightHistoryViewModel) viewModelOf(::BaseViewModel) viewModelOf(::SearchViewModel) viewModelOf(::ChooseSeatViewModel) - viewModel { (extras: Bundle?) -> - FlightDetailHistoryViewModel( - extras = extras, - historyRepository = get(), - ) - } + viewModelOf(::FlightDetailHistoryViewModel) viewModelOf(::SharedViewModelEditProfile) viewModelOf(::BookersBiodataViewModel) - viewModelOf(::PassengerBiodataViewModel) viewModelOf(::CheckoutTicketViewModel) viewModelOf(::FlightDetailViewModel) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/account/AccountFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/account/AccountFragment.kt index 23d5bcd..be00f1d 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/account/AccountFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/account/AccountFragment.kt @@ -7,8 +7,8 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController import com.kom.skyfly.R import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.databinding.FragmentAccountBinding @@ -16,6 +16,8 @@ import com.kom.skyfly.presentation.account.editprofile.BottomSheetsChangePasswor import com.kom.skyfly.presentation.account.editprofile.BottomSheetsEditProfile import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen import es.dmoral.toasty.Toasty import org.koin.androidx.viewmodel.ext.android.viewModel @@ -28,6 +30,8 @@ class AccountFragment : Fragment() { private var email: String? = null private var fullName: String? = null private var phoneNumber: String? = null + private var familyName: String? = null + private var id: String? = null override fun onCreateView( @@ -76,7 +80,27 @@ class AccountFragment : Fragment() { ).show() }, doOnError = { - Log.d("req-changePassword", "reqChangePassword: ${it.exception?.message}") + if (it.exception is NoInternetException) { + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.no_internet_connection), + ) + } else if (it.exception is UnAuthorizeException) { + binding.csvProfile.isVisible = true + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + (activity as BaseActivity).errorHandler(it.exception) + } else if (it.exception is ServerErrorException) { + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_server_error_please_try_again_later), + ) + (activity as BaseActivity).errorHandler(it.exception) + } else { + Log.d("req-changePassword", "reqChangePassword: ${it.exception?.message}") + } }, ) } @@ -84,19 +108,21 @@ class AccountFragment : Fragment() { private fun setClickListeners() { binding.layoutBtnProfile.tvEditProfile.setOnClickListener { - fullName?.let { fullName -> - phoneNumber?.let { phoneNumber -> - id?.let { id -> - doEditProfile(id, fullName, phoneNumber) + familyName?.let { familyNames -> + fullName?.let { fullName -> + phoneNumber?.let { phoneNumber -> + id?.let { id -> + doEditProfile(id, fullName, phoneNumber, familyNames) + Toast.makeText(requireContext(), "tes", Toast.LENGTH_SHORT).show() + } } } } } binding.layoutBtnProfile.tvLogoutProfile.setOnClickListener { accountViewModel.doLogout(null) - (activity as BaseActivity).handleUnAuthorize() + (activity as BaseActivity).doLogoutHandler() Toasty.normal(requireContext(), "Success!", Toast.LENGTH_SHORT).show() - navigateToHome() } binding.layoutBtnProfile.tvChangePassword.setOnClickListener { doChangePassword() @@ -110,17 +136,33 @@ class AccountFragment : Fragment() { private fun observeLoginStatus() { accountViewModel.isUserLoggedIn().observe(viewLifecycleOwner) { result -> result.proceedWhen( - doOnSuccess = { isLoggedIn -> + doOnSuccess = { getProfileData() }, doOnError = { - val isUserLoggedIn = it.payload?.status.toBoolean() - if (!isUserLoggedIn) { - (activity as BaseActivity).handleUnAuthorize() - Toasty.error(requireContext(), "Session expired. Please log in again.", Toast.LENGTH_SHORT, true).show() + if (it.exception is NoInternetException) { + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.no_internet_connection), + ) + } else if (it.exception is UnAuthorizeException) { + (activity as BaseActivity).errorHandler(it.exception) + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (it.exception is ServerErrorException) { + (activity as BaseActivity).errorHandler(it.exception) + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_server_error_please_try_again_later), + ) } else { + Log.d( + "login-status", + "Error checking login status: ${it.exception?.message}", + ) } - Log.d("login-status", "Error checking login status: ${it.exception?.message}") }, ) } @@ -134,10 +176,12 @@ class AccountFragment : Fragment() { id = data?.userId email = data?.email fullName = data?.fullName + familyName = data?.familyName phoneNumber = data?.phoneNumber binding.layoutProfileUser.etEmail.setText(data?.email) binding.layoutProfileUser.etFullName.setText(data?.fullName) binding.layoutProfileUser.etPhoneNumber.setText(data?.phoneNumber) + binding.layoutProfileUser.etFamilyName.setText(data?.familyName) binding.srfProfile.isRefreshing = false } }, @@ -146,30 +190,36 @@ class AccountFragment : Fragment() { if (it.exception is NoInternetException) { binding.csvProfile.setState( ContentState.ERROR_NETWORK_GENERAL, - "Tidak ada internet!", + getString(R.string.no_internet_connection), + ) + } else if (it.exception is UnAuthorizeException) { + (activity as BaseActivity).errorHandler(it.exception) + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (it.exception is ServerErrorException) { + (activity as BaseActivity).errorHandler(it.exception) + binding.csvProfile.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_server_error_please_try_again_later), ) - } - val errorMessage = it.exception?.message - if (errorMessage != null && errorMessage.contains("jwt expired")) { - (activity as BaseActivity).handleUnAuthorize() } else { - Log.d("get-profile", "getProfileData: ${it.exception?.message}") + Log.d("get-profile", "getProfileData: ${it.exception}") } }, ) } } - private fun navigateToHome() { - findNavController().navigate(R.id.menu_home_tab) - } - private fun doEditProfile( id: String, fullName: String, phoneNumber: String, + familyName: String, ) { - val bottomSheetFragment = BottomSheetsEditProfile.newInstance(id, fullName, phoneNumber) + val bottomSheetFragment = + BottomSheetsEditProfile.newInstance(id, fullName, phoneNumber, familyName) bottomSheetFragment.show(parentFragmentManager, bottomSheetFragment.tag) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsChangePassword.kt b/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsChangePassword.kt index 5b4010f..21ee87d 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsChangePassword.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsChangePassword.kt @@ -24,7 +24,8 @@ class BottomSheetsChangePassword : BottomSheetDialogFragment() { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - binding = FragmentBottomSheetsChangePasswordBinding.inflate(layoutInflater, container, false) + binding = + FragmentBottomSheetsChangePasswordBinding.inflate(layoutInflater, container, false) return binding.root } @@ -56,7 +57,7 @@ class BottomSheetsChangePassword : BottomSheetDialogFragment() { newPassword: String, confirmNewPassword: String, ) { - sharedViewModelEditProfile.updateProfile(null, null, newPassword, confirmNewPassword) + sharedViewModelEditProfile.updateProfile(null, null, null, newPassword, confirmNewPassword) .observe(viewLifecycleOwner) { result -> result.proceedWhen( doOnSuccess = { @@ -79,7 +80,7 @@ class BottomSheetsChangePassword : BottomSheetDialogFragment() { ).show() }, doOnLoading = { - binding.btnSubmit.isVisible = false + binding.btnSubmit.isEnabled = false binding.pbLoading.isVisible = true }, ) diff --git a/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsEditProfile.kt b/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsEditProfile.kt index 2711c9a..9811f91 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsEditProfile.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/BottomSheetsEditProfile.kt @@ -24,17 +24,20 @@ class BottomSheetsEditProfile : BottomSheetDialogFragment() { private const val ARG_FULL_NAME = "arg_full_name" private const val ARG_PHONE_NUMBER = "arg_phone_number" private const val ARG_USER_ID = "arg_user_id" + private const val ARG_FAMILY_NAME = "arg_family_name" fun newInstance( id: String?, fullName: String?, phoneNumber: String?, + familyName: String?, ): BottomSheetsEditProfile { val fragment = BottomSheetsEditProfile() val args = Bundle() args.putString(ARG_FULL_NAME, fullName) args.putString(ARG_PHONE_NUMBER, phoneNumber) args.putString(ARG_USER_ID, id) + args.putString(ARG_FAMILY_NAME, familyName) fragment.arguments = args return fragment } @@ -61,8 +64,10 @@ class BottomSheetsEditProfile : BottomSheetDialogFragment() { id = arguments?.getString(ARG_USER_ID) val fullName = arguments?.getString(ARG_FULL_NAME) val phoneNumber = arguments?.getString(ARG_PHONE_NUMBER) + val familyName = arguments?.getString(ARG_FAMILY_NAME) binding.etFullName.setText(fullName) binding.etPhoneNumber.setText(phoneNumber) + binding.etFamilyName.setText(familyName) } private fun setClickListeners() { @@ -82,7 +87,8 @@ class BottomSheetsEditProfile : BottomSheetDialogFragment() { private fun proceedEditProfile(phoneNumber: String) { val name = binding.etFullName.text.toString().trim() - sharedViewModelEditProfile.updateProfile(name, phoneNumber, null, null) + val familyName = binding.etFamilyName.text.toString().trim() + sharedViewModelEditProfile.updateProfile(name, phoneNumber, familyName, null, null) .observe(viewLifecycleOwner) { result -> result.proceedWhen( doOnSuccess = { @@ -105,7 +111,7 @@ class BottomSheetsEditProfile : BottomSheetDialogFragment() { ).show() }, doOnLoading = { - binding.btnSubmit.isVisible = false + binding.btnSubmit.isEnabled = false binding.pbLoading.isVisible = true }, ) diff --git a/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/SharedViewModelEditProfile.kt b/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/SharedViewModelEditProfile.kt index f133491..3f94d0c 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/SharedViewModelEditProfile.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/account/editprofile/SharedViewModelEditProfile.kt @@ -14,9 +14,10 @@ class SharedViewModelEditProfile(private val profileRepository: ProfileRepositor fun updateProfile( name: String?, phoneNumber: String?, + familyName: String?, password: String?, confirmPassword: String?, ) = profileRepository - .updateProfile(name, phoneNumber, password, confirmPassword) + .updateProfile(name, phoneNumber, familyName, password, confirmPassword) .asLiveData(Dispatchers.IO) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/BottomSheetsIssueTicket.kt b/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/IssueTicketBottomSheets.kt similarity index 93% rename from app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/BottomSheetsIssueTicket.kt rename to app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/IssueTicketBottomSheets.kt index 255cc55..c6daaba 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/BottomSheetsIssueTicket.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/IssueTicketBottomSheets.kt @@ -11,7 +11,7 @@ import com.kom.skyfly.R import com.kom.skyfly.databinding.FragmentBottomSheetsIssueTicketBinding import com.kom.skyfly.presentation.checkout.flightdetail.FlightDetailActivity -class BottomSheetsIssueTicket : BottomSheetDialogFragment() { +class IssueTicketBottomSheets : BottomSheetDialogFragment() { private lateinit var binding: FragmentBottomSheetsIssueTicketBinding private var listener: SearchView.OnCloseListener? = null @@ -21,8 +21,8 @@ class BottomSheetsIssueTicket : BottomSheetDialogFragment() { adult: Int, child: Int, baby: Int, - ): BottomSheetsIssueTicket { - val fragment = BottomSheetsIssueTicket() + ): IssueTicketBottomSheets { + val fragment = IssueTicketBottomSheets() val args = Bundle().apply { putString("transactionId", transactionId) @@ -80,6 +80,7 @@ class BottomSheetsIssueTicket : BottomSheetDialogFragment() { putExtra("EXTRAS_ADULT", adult) putExtra("EXTRAS_CHILD", child) putExtra("EXTRAS_BABY", baby) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } startActivity(intent) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/BottomSheetsDialogFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/NotLoginBottomSheets.kt similarity index 86% rename from app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/BottomSheetsDialogFragment.kt rename to app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/NotLoginBottomSheets.kt index f5b67a3..fcc1df6 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/BottomSheetsDialogFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/bottomsheetsdialog/NotLoginBottomSheets.kt @@ -12,8 +12,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.kom.skyfly.R import com.kom.skyfly.databinding.FragmentBottomSheetsDialogBinding import com.kom.skyfly.presentation.login.LoginActivity +import com.kom.skyfly.presentation.main.MainActivity -class BottomSheetsDialogFragment : BottomSheetDialogFragment() { +class NotLoginBottomSheets : BottomSheetDialogFragment() { private lateinit var binding: FragmentBottomSheetsDialogBinding private var listener: SearchView.OnCloseListener? = null @@ -46,11 +47,18 @@ class BottomSheetsDialogFragment : BottomSheetDialogFragment() { dismiss() } binding.ivClose.setOnClickListener { + navigateToHome() listener?.onClose() dismiss() } } + private fun navigateToHome() { + val intent = Intent(requireContext(), MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + } + private fun navigateToLogin() { val intent = Intent(requireContext(), LoginActivity::class.java) startActivity(intent) diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/bookersbiodata/BookersBiodataActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/bookersbiodata/BookersBiodataActivity.kt index 8977881..c97ca59 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/bookersbiodata/BookersBiodataActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/bookersbiodata/BookersBiodataActivity.kt @@ -6,10 +6,13 @@ import android.util.Log import androidx.core.view.isVisible import com.kom.skyfly.R import com.kom.skyfly.core.BaseActivity +import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket import com.kom.skyfly.databinding.ActivityOrdererBiodataBinding import com.kom.skyfly.presentation.checkout.passengerbiodata.PassengerBiodataActivity import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen import org.koin.androidx.viewmodel.ext.android.viewModel @@ -23,10 +26,18 @@ class BookersBiodataActivity : BaseActivity() { private var familyName: String? = null private var phoneNumber: String? = null private var id: String? = null + private var flightDetailTicket: FlightDetailTicket? = null + private var adultCount: Int? = 0 + private var childCount: Int? = 0 + private var babyCount: Int? = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) + flightDetailTicket = intent.getParcelableExtra("EXTRAS_FLIGHT_DETAIL") + adultCount = intent.getIntExtra("EXTRA_ADULT_COUNT", 0) + childCount = intent.getIntExtra("EXTRA_CHILD_COUNT", 0) + babyCount = intent.getIntExtra("EXTRA_BABY_COUNT", 0) setHaveFamilyName() setTitleHeader() getProfileData() @@ -36,10 +47,14 @@ class BookersBiodataActivity : BaseActivity() { private fun navigateToPassengerBiodata() { val intent = Intent(this, PassengerBiodataActivity::class.java).apply { + putExtra("EXTRAS_FLIGHT_DATA", flightDetailTicket) putExtra("EXTRAS_FULL_NAME", fullName) putExtra("EXTRAS_FAMILY_NAME", familyName) putExtra("EXTRAS_EMAIL", email) putExtra("EXTRAS_PHONE_NUMBER", phoneNumber) + putExtra("EXTRA_ADULT_COUNT", adultCount) + putExtra("EXTRA_CHILD_COUNT", childCount) + putExtra("EXTRA_BABY_COUNT", babyCount) } startActivity(intent) } @@ -51,6 +66,9 @@ class BookersBiodataActivity : BaseActivity() { binding.btnSave.setOnClickListener { navigateToPassengerBiodata() } + binding.layoutHeader.ivBack.setOnClickListener { + onBackPressed() + } } private fun setTitleHeader() { @@ -65,11 +83,15 @@ class BookersBiodataActivity : BaseActivity() { tilFamilyName.isVisible = true etFamilyName.isVisible = true tvFamilyName.isVisible = true + val trackDrawable = scHaveFamilyName.trackDrawable + trackDrawable?.setTint(resources.getColor(R.color.md_theme_primaryFixed_mediumContrast)) } else { tvFamilyName.isVisible = false tilFamilyName.isVisible = false etFamilyName.isVisible = false tvFamilyName.isVisible = false + val trackDrawable = scHaveFamilyName.trackDrawable + trackDrawable?.setTint(resources.getColor(R.color.grey)) } } } @@ -79,12 +101,15 @@ class BookersBiodataActivity : BaseActivity() { bookersBiodataViewModel.getProfile().observe(this) { result -> result.proceedWhen( doOnSuccess = { + binding.btnSave.isEnabled = true it.payload.let { data -> id = data?.userId email = data?.email familyName = data?.familyName fullName = data?.fullName phoneNumber = data?.phoneNumber + binding.layoutHeader.tvTitleHeader.text = + getString(R.string.text_bookers_biodata) binding.layoutFormCustomerBiodata.etFullName.setText(data?.fullName) binding.layoutFormCustomerBiodata.etEmail.setText(data?.email) binding.layoutFormCustomerBiodata.etNoTlp.setText(data?.phoneNumber) @@ -93,20 +118,32 @@ class BookersBiodataActivity : BaseActivity() { } }, doOnError = { + binding.btnSave.isEnabled = false binding.main.isRefreshing = false if (it.exception is NoInternetException) { binding.csvBookers.setState( ContentState.ERROR_NETWORK_GENERAL, - "No internet connection!!", + getString(R.string.no_internet_connection), + ) + } else if (it.exception is UnAuthorizeException) { + errorHandler(it.exception) + binding.csvBookers.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (it.exception is ServerErrorException) { + errorHandler(it.exception) + binding.csvBookers.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_server_error_please_try_again_later), ) - } - val errorMessage = it.exception?.message - if (errorMessage != null && errorMessage.contains("jwt expired")) { - handleUnAuthorize() } else { Log.d("get-profile", "getProfileData: ${it.exception?.message}") } }, + doOnLoading = { + binding.btnSave.isEnabled = false + }, ) } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/checkoutticket/CheckoutTicketActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/checkoutticket/CheckoutTicketActivity.kt index 8275afd..526642c 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/checkoutticket/CheckoutTicketActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/checkoutticket/CheckoutTicketActivity.kt @@ -4,17 +4,21 @@ import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.util.Log -import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.kom.skyfly.R +import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.data.model.transaction.detail.TransactionDetailResponses import com.kom.skyfly.databinding.ActivityCheckoutTicketBinding import com.kom.skyfly.presentation.checkout.payment.PaymentActivity +import com.kom.skyfly.presentation.common.views.ContentState +import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.formatToRupiah import com.kom.skyfly.utils.proceedWhen import org.koin.androidx.viewmodel.ext.android.viewModel -class CheckoutTicketActivity : AppCompatActivity() { +class CheckoutTicketActivity : BaseActivity() { private val binding: ActivityCheckoutTicketBinding by lazy { ActivityCheckoutTicketBinding.inflate(layoutInflater) } @@ -45,16 +49,51 @@ class CheckoutTicketActivity : AppCompatActivity() { checkoutTicketViewModel.getTransactionById(id).observe(this) { result -> result.proceedWhen( doOnSuccess = { + binding.btnProceedToPayment.isEnabled = true + binding.main.isRefreshing = false + binding.shmProgressFlightTicket.isVisible = false + binding.layoutFlightDetails.cvFlightDetails.isVisible = true + binding.layoutFlightDetails.cvPriceDetails.isVisible = true + binding.layoutFlightDetails.tvTrip.isVisible = true val response = result.payload setTransactionDetailData(response) - Log.d("GetTransactionById", "getTransactionDataById: $response") }, doOnError = { + binding.btnProceedToPayment.isEnabled = false + binding.shmProgressFlightTicket.isVisible = false + binding.main.isRefreshing = false + if (it.exception is NoInternetException) { + binding.csvCheckoutTicket.setState( + ContentState.ERROR_NETWORK_GENERAL, + "No internet connection!!", + ) + } else if (it.exception is UnAuthorizeException) { + errorHandler(it.exception) + binding.csvCheckoutTicket.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (it.exception is ServerErrorException) { + errorHandler(it.exception) + binding.csvCheckoutTicket.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_something_went_wrong), + R.drawable.img_empty_data, + ) + } Log.d( "GetTransactionByIdError", "getTransactionDataById: ${it.exception?.message}", ) }, + doOnLoading = { + binding.layoutFlightDetails.cvFlightDetails.isVisible = false + binding.layoutFlightDetails.cvPriceDetails.isVisible = false + binding.layoutFlightDetails.tvTrip.isVisible = false + binding.main.isRefreshing = true + binding.btnProceedToPayment.isEnabled = false + binding.shmProgressFlightTicket.isVisible = true + }, ) } } @@ -166,6 +205,12 @@ class CheckoutTicketActivity : AppCompatActivity() { intent.putExtra("EXTRAS_BABY", baby) startActivity(intent) } + binding.layoutHeader.ivBack.setOnClickListener { + onBackPressed() + } + binding.main.setOnRefreshListener { + getTransactionDataById(transactionId) + } } private fun setTitleHeader() { diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatActivity.kt index 1bddaa4..f3b4e14 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatActivity.kt @@ -1,28 +1,32 @@ package com.kom.skyfly.presentation.checkout.chooseseat +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.util.Log import android.view.View import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import com.kom.skyfly.R +import com.kom.skyfly.core.BaseActivity +import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket import com.kom.skyfly.data.model.passenger.PassengerData import com.kom.skyfly.data.source.network.model.transaction.request.Bookers import com.kom.skyfly.data.source.network.model.transaction.request.TransactionRequest import com.kom.skyfly.databinding.ActivityChooseSeatBinding -import com.kom.skyfly.presentation.checkout.bookersbiodata.BookersBiodataActivity import com.kom.skyfly.presentation.checkout.checkoutticket.CheckoutTicketActivity import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen import dev.jahidhasanco.seatbookview.SeatBookView import dev.jahidhasanco.seatbookview.SeatClickListener import es.dmoral.toasty.Toasty import org.koin.androidx.viewmodel.ext.android.viewModel +import java.util.Locale -class ChooseSeatActivity : AppCompatActivity() { +class ChooseSeatActivity : BaseActivity() { private val binding: ActivityChooseSeatBinding by lazy { ActivityChooseSeatBinding.inflate(layoutInflater) } @@ -51,27 +55,29 @@ class ChooseSeatActivity : AppCompatActivity() { private var baby: Int = 0 private var paymentUrl: String? = null private var transactionId: String? = null + private var flightDetailTicket: FlightDetailTicket? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - + flightDetailTicket = intent.getParcelableExtra("EXTRAS_FLIGHT_DETAIL") fullNames = intent.getStringExtra("EXTRAS_FULL_NAME") familyName = intent.getStringExtra("EXTRAS_FAMILY_NAME") email = intent.getStringExtra("EXTRAS_EMAIL") phoneNumber = intent.getStringExtra("EXTRAS_PHONE_NUMBER") passengerDataList = intent.getParcelableArrayListExtra("EXTRAS_PASSENGERS")!! + adult = intent.getIntExtra("EXTRAS_ADULT", 0) children = intent.getIntExtra("EXTRAS_CHILD", 0) baby = intent.getIntExtra("EXTRAS_BABY", 0) - passengerDataList.forEachIndexed { index, passenger -> - Log.d("PassengerData", "Passenger $index: $passenger") - } + setTitleHeader() - flightId = "clxrh0w6m000zokfpvb5sobt8" - getAllFlightSeatData(flightId!!) - getFlightSeatData(flightId!!) - getFlightPrice(flightId!!) + flightId = flightDetailTicket?.id + flightId?.let { + getAllFlightSeatData(it) + getFlightSeatData(it) + getFlightPrice(it) + } setOnClickListener() } @@ -79,12 +85,25 @@ class ChooseSeatActivity : AppCompatActivity() { binding.btnSave.setOnClickListener { flightId?.let { createTransaction(it, passengerDataList) } } + binding.layoutHeader.ivBack.setOnClickListener { + onBackPressed() + } + binding.main.setOnRefreshListener { + flightId?.let { + getAllFlightSeatData(it) + getFlightSeatData(it) + getFlightPrice(it) + } + } } private fun getFlightSeatData(flightId: String) { chooseSeatViewModel.getFlightSeat(flightId).observe(this) { result -> result.proceedWhen( doOnSuccess = { resultWrapper -> + binding.btnSave.isEnabled = true + binding.pbLoading.isVisible = false + binding.main.isRefreshing = false val payload = resultWrapper.payload chooseSeatViewModel.setSeatList(payload?.flightSeat.orEmpty()) if (payload != null) { @@ -99,46 +118,79 @@ class ChooseSeatActivity : AppCompatActivity() { binding.csvSeatView.setState(ContentState.SUCCESS) }, doOnEmpty = { + binding.btnSave.isEnabled = true + binding.pbLoading.isVisible = false binding.shmProgressSeatView.isVisible = false binding.csvSeatView.setState(ContentState.EMPTY, "Empty seat!") }, doOnError = { error -> + binding.btnSave.isEnabled = false + binding.main.isRefreshing = false binding.shmProgressSeatView.isVisible = false if (error.exception is NoInternetException) { - binding.csvSeatView.setState(ContentState.ERROR_NETWORK) + binding.csvSeatView.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + errorHandler(error.exception) + binding.csvSeatView.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + errorHandler(error.exception) + binding.csvSeatView.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) } else { binding.csvSeatView.setState(ContentState.ERROR_GENERAL) } Log.e("ChooseSeatActivity", "Error: ${error.exception?.message}") }, doOnLoading = { + binding.btnSave.isEnabled = false + binding.main.isRefreshing = true binding.shmProgressSeatView.isVisible = true }, ) } } + @SuppressLint("StringFormatMatches") private fun getAllFlightSeatData(flightId: String) { chooseSeatViewModel.getAllFlightSeat(flightId).observe(this) { result -> result.proceedWhen( doOnSuccess = { resultWrapper -> - Log.d("SeatDatas", "getAllFlightSeatData: $resultWrapper") + binding.main.isRefreshing = false + binding.btnSave.isEnabled = true + binding.pbLoading.isVisible = false seatTotal = resultWrapper.payload?.size ?: 0 resultWrapper.payload.let { it?.map { response -> seatType = response.type.orEmpty() - binding.tvSeatTitle.text = "$seatType - $seatTotal Seat" + binding.tvSeatTitle.text = + getString(R.string.text_seat_data_title, seatType, seatTotal) } } }, doOnLoading = { - binding.tvSeatTitle.text = "Loading..." + binding.btnSave.isEnabled = false + binding.main.isRefreshing = false + binding.tvSeatTitle.text = getString(R.string.text_loading) }, doOnEmpty = { - binding.tvSeatTitle.text = "SeatView Kosong" + binding.btnSave.isEnabled = true + binding.pbLoading.isVisible = false + binding.main.isRefreshing = false + binding.tvSeatTitle.text = getString(R.string.text_empty_seat) }, doOnError = { - binding.tvSeatTitle.text = "Error" + binding.btnSave.isEnabled = false + binding.main.isRefreshing = true + binding.tvSeatTitle.text = getString(R.string.text_empty_seat) }, ) } @@ -148,6 +200,9 @@ class ChooseSeatActivity : AppCompatActivity() { chooseSeatViewModel.getFlightPrice(flightId).observe(this) { result -> result.proceedWhen( doOnSuccess = { + binding.main.isRefreshing = false + binding.btnSave.isEnabled = true + binding.pbLoading.isVisible = false val payload = it.payload payload?.let { response -> seatId = response.first @@ -177,21 +232,12 @@ class ChooseSeatActivity : AppCompatActivity() { val actualSelectedIdx = selectedIdList.map { it - 1 } chooseSeatViewModel.setSelectedSeatList(actualSelectedIdx) - if (currentPassengerIndex >= passengerDataList.size) { - Toasty.info( - this@ChooseSeatActivity, - "All passengers have been assigned seats.", - Toast.LENGTH_SHORT, - ).show() - return - } - val selectedSeatIndex = selectedIdList.firstOrNull() if (selectedSeatIndex == null) { Toasty.error( this@ChooseSeatActivity, - "No seat selected.", + getString(R.string.text_no_seat_selected), Toast.LENGTH_SHORT, ).show() return @@ -201,29 +247,17 @@ class ChooseSeatActivity : AppCompatActivity() { if (correctedSeatIndex < 0 || correctedSeatIndex >= seatId.size) { Toasty.error( this@ChooseSeatActivity, - "Invalid seat selected.", + getString(R.string.text_invalid_seat_selected), Toast.LENGTH_SHORT, ).show() return } - val selectedSeatId = seatId[correctedSeatIndex] - val currentPassenger = passengerDataList[currentPassengerIndex] - - Toasty.success( - this@ChooseSeatActivity, - "Assigned seat $selectedSeatId to ${currentPassenger.fullName}", - Toast.LENGTH_SHORT, - ).show() - currentPassengerIndex++ if (currentPassengerIndex < passengerDataList.size) { - Toasty.warning( - this@ChooseSeatActivity, - "Select seat for ${passengerDataList[currentPassengerIndex].fullName}", - Toast.LENGTH_SHORT, - ).show() + binding.btnSave.isEnabled = false } else { + binding.btnSave.isEnabled = true Toasty.info( this@ChooseSeatActivity, "All passengers have been assigned seats.", @@ -235,7 +269,7 @@ class ChooseSeatActivity : AppCompatActivity() { override fun onBookedSeatClick(view: View) { Toasty.info( this@ChooseSeatActivity, - "Seat is Booked!", + getString(R.string.text_seat_is_booked), Toast.LENGTH_SHORT, ).show() } @@ -243,7 +277,7 @@ class ChooseSeatActivity : AppCompatActivity() { override fun onReservedSeatClick(view: View) { Toasty.info( this@ChooseSeatActivity, - "Seat is Reserved!", + getString(R.string.text_seat_is_reserved), Toast.LENGTH_SHORT, ).show() } @@ -273,10 +307,7 @@ class ChooseSeatActivity : AppCompatActivity() { .observe(this) { result -> result.proceedWhen( doOnSuccess = { - Toasty.success(this, "Success Create Transaction!!!!!", Toast.LENGTH_SHORT) - .show() - val intent = Intent(this, BookersBiodataActivity::class.java) - startActivity(intent) + binding.main.isRefreshing = false it.payload?.let { response -> paymentUrl = response.redirectUrl transactionId = response.transactionId @@ -284,18 +315,28 @@ class ChooseSeatActivity : AppCompatActivity() { navigateToPayment() }, doOnError = { + binding.main.isRefreshing = false Log.e("ChooseSeatActivity", "Error: ${it.exception?.message}") }, doOnLoading = { + binding.main.isRefreshing = true binding.pbLoading.isVisible = true - binding.btnSave.isVisible = false + binding.btnSave.isEnabled = false }, ) } } private fun setTitleHeader() { - binding.layoutHeader.tvTitleHeader.text = getString(R.string.text_header_choose_seat) + binding.layoutHeader.tvTitleHeader.text = + getString( + R.string.choose_seat, + flightDetailTicket?.departureCountryCode, + flightDetailTicket?.arrivalCountryCode, + flightDetailTicket?.seatClass?.lowercase( + Locale.ROOT, + ), + ) } private fun navigateToPayment() { diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatViewModel.kt index ea6c87e..24ba461 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/chooseseat/ChooseSeatViewModel.kt @@ -46,7 +46,7 @@ class ChooseSeatViewModel( fullName = passenger.fullName, dob = "${passenger.dob} 10:00:00", validityPeriod = "${passenger.validityPeriod} 10:00:00", - familyName = passenger.familyName, + familyName = passenger.familyName.orEmpty(), citizenship = passenger.citizenship, passport = passenger.passport, issuingCountry = "Indonesia", diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/flightdetail/FlightDetailActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/flightdetail/FlightDetailActivity.kt index 7c1cc9c..1aa8d12 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/flightdetail/FlightDetailActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/flightdetail/FlightDetailActivity.kt @@ -1,19 +1,27 @@ package com.kom.skyfly.presentation.checkout.flightdetail +import android.annotation.SuppressLint +import android.content.Intent import android.content.res.ColorStateList import android.os.Bundle import android.util.Log -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import coil.load import com.kom.skyfly.R +import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.data.model.transaction.detail.TransactionDetailResponses import com.kom.skyfly.databinding.ActivityFlightDetailBinding +import com.kom.skyfly.presentation.common.views.ContentState +import com.kom.skyfly.presentation.main.MainActivity +import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.formatToRupiah import com.kom.skyfly.utils.proceedWhen import org.koin.androidx.viewmodel.ext.android.viewModel -class FlightDetailActivity : AppCompatActivity() { +class FlightDetailActivity : BaseActivity() { private val binding: ActivityFlightDetailBinding by lazy { ActivityFlightDetailBinding.inflate(layoutInflater) } @@ -27,14 +35,35 @@ class FlightDetailActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - setSwipeRefresh() transactionId = intent.getStringExtra("EXTRAS_TRANSACTION_ID") adult = intent.getIntExtra("EXTRAS_ADULT", 0) children = intent.getIntExtra("EXTRAS_CHILD", 0) baby = intent.getIntExtra("EXTRAS_BABY", 0) + setTitleHeader() + setSwipeRefresh() + setOnClickListeners() getTicketDetail(transactionId) } + private fun setOnClickListeners() { + binding.layoutHeader.ivBack.setOnClickListener { + navigateToHome() + } + binding.btnToHome.setOnClickListener { + navigateToHome() + } + } + + private fun navigateToHome() { + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + } + + private fun setTitleHeader() { + binding.layoutHeader.tvTitleHeader.text = getString(R.string.text_ticket_detail) + } + private fun setSwipeRefresh() { binding.main.setOnRefreshListener { getTicketDetail(transactionId) @@ -46,40 +75,76 @@ class FlightDetailActivity : AppCompatActivity() { viewModel.getTransactionById(id).observe(this) { result -> result.proceedWhen( doOnSuccess = { + binding.btnToHome.isEnabled = true + binding.layoutFlightDetails.cvFlightDetails.isVisible = true + binding.layoutFlightDetails.cvPriceDetails.isVisible = true + binding.layoutFlightDetails.tvPaymentStatus.isVisible = true binding.main.isRefreshing = false val response = result.payload + binding.shmProgressFlightTicket.isVisible = false setTransactionDetailData(response) Log.d("GetTransactionById", "getTransactionDataById: $response") }, doOnError = { + binding.btnToHome.isEnabled = true binding.main.isRefreshing = false + binding.shmProgressFlightTicket.isVisible = false + when (it.exception) { + is NoInternetException -> { + binding.csvFlightDetail.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.no_internet_connection), + ) + } + + is UnAuthorizeException -> { + errorHandler(it.exception) + binding.csvFlightDetail.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } + + is ServerErrorException -> { + errorHandler(it.exception) + binding.csvFlightDetail.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_something_went_wrong), + R.drawable.img_empty_data, + ) + } + } Log.d( "GetTransactionByIdError", "getTransactionDataById: ${it.exception?.message}", ) }, doOnLoading = { + binding.csvFlightDetail.isVisible = false + binding.btnToHome.isEnabled = false + binding.layoutFlightDetails.cvFlightDetails.isVisible = false + binding.layoutFlightDetails.cvPriceDetails.isVisible = false + binding.layoutFlightDetails.tvPaymentStatus.isVisible = false binding.main.isRefreshing = true + binding.shmProgressFlightTicket.isVisible = true }, ) } } } + @SuppressLint("StringFormatMatches") private fun setTransactionDetailData(response: TransactionDetailResponses?) { var departureDate: String? = null var departureTime: String? = null var departureAirport: String? = null var departureTerminal: String? = null - var departureCity: String? = null var airlines: String? = null var seatClass: String? = null var flightNumber: String? = null var arrivalDate: String? = null var arrivalTime: String? = null var destinationAirport: String? = null - var destinationCity: String? = null - var flightDuration: String? = null val tax: Int? = response?.data?.tax val totalPrice: Int? = response?.data?.totalPrice var totalPricePassengerAdult: Int? = 0 @@ -89,36 +154,48 @@ class FlightDetailActivity : AppCompatActivity() { val paymentStatus: String? = response?.data?.status val bookingCode: String? = response?.data?.booking?.code val transactionDetails = response?.data?.transactionDetails + var airlinesImg: String? = null response?.data?.transactionDetails?.map { departureDate = it?.flight?.departure?.date departureTime = it?.flight?.departure?.time - departureCity = it?.flight?.departureAirport?.city arrivalDate = it?.flight?.arrival?.date arrivalTime = it?.flight?.arrival?.time departureAirport = it?.flight?.departureAirport?.name departureTerminal = it?.flight?.airline?.terminal - destinationCity = it?.flight?.destinationAirport?.city flightNumber = it?.flight?.airline?.code destinationAirport = it?.flight?.destinationAirport?.name - flightDuration = it?.flight?.flightDuration airlines = it?.flight?.airline?.name seatClass = it?.seat?.type passengerCategory = it?.passengerCategory - if (passengerCategory == "ADULT") { - totalPricePassengerAdult = it?.totalPrice?.times(adult) - } else if (passengerCategory == "CHILD") { - totalPricePassengerChild = it?.totalPrice?.times(children) - } else if (passengerCategory == "INFRANT") { - totalPricePassengerBaby = it?.totalPrice?.times(baby) + airlinesImg = it?.flight?.airline?.image + when (passengerCategory) { + "ADULT" -> { + totalPricePassengerAdult = it?.totalPrice?.times(adult) + } + + "CHILD" -> { + totalPricePassengerChild = it?.totalPrice?.times(children) + } + + "INFRANT" -> { + totalPricePassengerBaby = it?.totalPrice?.times(baby) + } } } + binding.layoutFlightDetails.tvTitlePassenger.text = getString(R.string.text_empty) + binding.layoutFlightDetails.tvCitizenship.text = getString(R.string.text_empty) with(binding.layoutFlightDetails) { + ivAirline.load( + airlinesImg, + ) { + error(R.drawable.img_airline) + } tvDetailDepartureDate.text = departureDate tvDetailDepartureTime.text = departureTime - tvDetailDepartureAirport.text = "$departureAirport -" - tvDetailTerminal.text = departureTerminal - tvDetailAirline.text = "$airlines - " + tvDetailDepartureAirport.text = + getString(R.string.text_departure_terminal, departureAirport, departureTerminal) + tvDetailAirline.text = getString(R.string.text_detail_airlines, airlines) tvDetailClass.text = seatClass tvDetailFlightNumber.text = flightNumber tvDetailArrivalDate.text = arrivalDate @@ -130,11 +207,11 @@ class FlightDetailActivity : AppCompatActivity() { transactionDetails?.forEachIndexed { index, detail -> val passengerName = detail?.name ?: "Unknown" - val citizenship = detail?.citizenship ?: "Unknown" + val userPassportId = detail?.passport ?: "Unknown" tvTitlePassenger.append( "Passenger ${index + 1} : $passengerName\n" + - "Citizenship : $citizenship\n", + "ID : $userPassportId\n", ) } @@ -168,7 +245,7 @@ class FlightDetailActivity : AppCompatActivity() { } if (adult > 0) { - tvTotalByAgeGroupAdult.text = "$adult Adult" + tvTotalByAgeGroupAdult.text = getString(R.string.text_adult_total, adult) tvTotalPriceByAgeGroupAdult.text = totalPricePassengerAdult.formatToRupiah().toString() tvTotalByAgeGroupAdult.isVisible = true @@ -179,7 +256,7 @@ class FlightDetailActivity : AppCompatActivity() { } if (children > 0) { - tvTotalByAgeGroupChild.text = "$children Child" + tvTotalByAgeGroupChild.text = getString(R.string.text_child_total, children) tvTotalPriceByAgeGroupChild.text = totalPricePassengerChild.formatToRupiah().toString() tvTotalByAgeGroupChild.isVisible = true @@ -190,7 +267,7 @@ class FlightDetailActivity : AppCompatActivity() { } if (baby > 0) { - tvTotalByAgeGroupBaby.text = "$baby Baby" + tvTotalByAgeGroupBaby.text = getString(R.string.text_baby_total, baby) tvTotalPriceByAgeGroupBaby.text = totalPricePassengerBaby.formatToRupiah().toString() tvTotalByAgeGroupBaby.isVisible = true diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataActivity.kt index 25a61bd..443b8c4 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataActivity.kt @@ -8,13 +8,13 @@ import android.widget.EditText import androidx.recyclerview.widget.LinearLayoutManager import com.kom.skyfly.R import com.kom.skyfly.core.BaseActivity +import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket import com.kom.skyfly.data.model.passenger.PassengerData import com.kom.skyfly.databinding.ActivityPassengerBiodataBinding import com.kom.skyfly.presentation.checkout.chooseseat.ChooseSeatActivity import com.kom.skyfly.presentation.checkout.passengerbiodata.adapter.PassengerItem import com.xwray.groupie.GroupAdapter import com.xwray.groupie.GroupieViewHolder -import org.koin.androidx.viewmodel.ext.android.viewModel import java.util.Calendar class PassengerBiodataActivity : BaseActivity() { @@ -22,7 +22,6 @@ class PassengerBiodataActivity : BaseActivity() { private var passengerDataList: List = emptyList() private val groupAdapter = GroupAdapter() - private val passengerBiodataViewModel: PassengerBiodataViewModel by viewModel() private var email: String? = null private var fullNames: String? = null @@ -31,18 +30,20 @@ class PassengerBiodataActivity : BaseActivity() { private var adult = 0 private var children = 0 private var baby = 0 + private var flightDetailTicket: FlightDetailTicket? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityPassengerBiodataBinding.inflate(layoutInflater) setContentView(binding.root) + flightDetailTicket = intent.getParcelableExtra("EXTRAS_FLIGHT_DATA") fullNames = intent.getStringExtra("EXTRAS_FULL_NAME") familyName = intent.getStringExtra("EXTRAS_FAMILY_NAME") email = intent.getStringExtra("EXTRAS_EMAIL") phoneNumber = intent.getStringExtra("EXTRAS_PHONE_NUMBER") - adult = intent.getIntExtra("EXTRAS_ADULT", 0) - children = intent.getIntExtra("EXTRAS_CHILD", 0) - baby = intent.getIntExtra("EXTRAS_BABY", 0) + adult = intent.getIntExtra("EXTRA_ADULT_COUNT", 0) + children = intent.getIntExtra("EXTRA_CHILD_COUNT", 0) + baby = intent.getIntExtra("EXTRA_BABY_COUNT", 0) setUpRecyclerView() setTitleHeader() handleNextButtonClick() @@ -56,10 +57,6 @@ class PassengerBiodataActivity : BaseActivity() { } private fun setUpRecyclerView() { - adult = 1 - children = 0 - baby = 0 - binding.rvPassengerForm.layoutManager = LinearLayoutManager(this) binding.rvPassengerForm.adapter = groupAdapter @@ -76,6 +73,7 @@ class PassengerBiodataActivity : BaseActivity() { i < adult + children -> "CHILD" else -> "INFRANT" } + groupAdapter.add( PassengerItem( passengerType, @@ -125,6 +123,7 @@ class PassengerBiodataActivity : BaseActivity() { } val intent = Intent(this, ChooseSeatActivity::class.java) + intent.putExtra("EXTRAS_FLIGHT_DETAIL", flightDetailTicket) intent.putParcelableArrayListExtra("EXTRAS_PASSENGERS", ArrayList(passengerDataList)) intent.putExtra("EXTRAS_FULL_NAME", fullNames) intent.putExtra("EXTRAS_FAMILY_NAME", familyName) diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataViewModel.kt deleted file mode 100644 index 876d3c4..0000000 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/PassengerBiodataViewModel.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.kom.skyfly.presentation.checkout.passengerbiodata - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import com.kom.skyfly.data.repository.transaction.TransactionRepository -import com.kom.skyfly.data.source.network.model.transaction.request.TransactionRequest -import kotlinx.coroutines.Dispatchers - -/** -Written by Komang Yuda Saputra -Github : https://github.com/YudaSaputraa - **/ -class PassengerBiodataViewModel(private val transactionRepository: TransactionRepository) : - ViewModel() { - fun createTransaction( - flightId: String, - adult: Int, - child: Int, - baby: Int, - transactionRequest: TransactionRequest, - ) = transactionRepository.createTransaction(flightId, adult, child, baby, transactionRequest) - .asLiveData(Dispatchers.IO) -} diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerAdapter.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerAdapter.kt deleted file mode 100644 index ca8819c..0000000 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerAdapter.kt +++ /dev/null @@ -1,173 +0,0 @@ -// package com.kom.skyfly.presentation.checkout.passengerbiodata.adapter -// -// import android.app.DatePickerDialog -// import android.view.LayoutInflater -// import android.view.ViewGroup -// import android.widget.ArrayAdapter -// import android.widget.EditText -// import android.widget.Spinner -// import androidx.core.view.isVisible -// import androidx.recyclerview.widget.RecyclerView -// import com.kom.skyfly.R -// import com.kom.skyfly.data.model.passenger.PassengerData -// import com.kom.skyfly.databinding.ItemFormPassengerBiodataBinding -// import java.util.Calendar -// -// -// class PassengerAdapter( -// private val recyclerView: RecyclerView, -// private val adultsCount: Int, -// private val childrenCount: Int, -// private val infantCount: Int, -// private val itemClick: (Int) -> Unit, -// ) : RecyclerView.Adapter() { -// private val passengerDataList = mutableListOf() -// -// override fun onCreateViewHolder( -// parent: ViewGroup, -// viewType: Int, -// ): PassengerViewHolder { -// val binding = -// ItemFormPassengerBiodataBinding.inflate( -// LayoutInflater.from(parent.context), -// parent, -// false, -// ) -// return PassengerViewHolder(binding) -// } -// -// override fun onBindViewHolder( -// holder: PassengerViewHolder, -// position: Int, -// ) { -// holder.bindView(position) -// } -// -// override fun getItemCount(): Int { -// return adultsCount + childrenCount + infantCount -// } -// -// inner class PassengerViewHolder( -// private val binding: ItemFormPassengerBiodataBinding, -// ) : RecyclerView.ViewHolder(binding.root) { -// init { -// ArrayAdapter.createFromResource( -// itemView.context, -// R.array.titles_array, -// android.R.layout.simple_spinner_item, -// ).also { adapter -> -// adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) -// binding.spinnerTitle.adapter = adapter -// } -// -// binding.scHaveFamilyName.setOnCheckedChangeListener { _, isChecked -> -// binding.tvFamilyName.isVisible = isChecked -// binding.tilFamilyName.isVisible = isChecked -// } -// binding.etDate.setOnClickListener { -// val position = adapterPosition -// showDatePickerDialog(binding.etDate) -// } -// binding.etValidUntil.setOnClickListener { -// val position = adapterPosition -// showDatePickerDialog(binding.etValidUntil) -// } -// -// binding.ivCalendar.setOnClickListener { -// val position = adapterPosition -// showDatePickerDialog(binding.etDate) -// } -// binding.ivCalendarValidUntil.setOnClickListener { -// val position = adapterPosition -// showDatePickerDialog(binding.etValidUntil) -// } -// } -// -// private fun showDatePickerDialog(editText: EditText) { -// val c = Calendar.getInstance() -// val year = c.get(Calendar.YEAR) -// val month = c.get(Calendar.MONTH) -// val day = c.get(Calendar.DAY_OF_MONTH) -// val datePickerDialog = -// DatePickerDialog( -// itemView.context, -// { _, year, monthOfYear, dayOfMonth -> -// val date = "$dayOfMonth/${monthOfYear + 1}/$year" -// editText.setText(date) -// }, -// year, -// month, -// day, -// ) -// datePickerDialog.show() -// } -// -// fun bindView(passengerNumber: Int) { -// itemView.apply { -// val passengerIndex = passengerNumber + 1 -// when { -// passengerNumber < adultsCount -> { -// binding.tvPassengerType.text = "Passenger $passengerIndex - Adult" -// } -// -// passengerNumber < adultsCount + childrenCount -> { -// binding.tvPassengerType.text = "Passenger $passengerIndex - Children" -// } -// -// else -> { -// binding.tvPassengerType.text = "Passenger $passengerIndex - Baby" -// } -// } -// binding.etFullName.setOnFocusChangeListener { _, hasFocus -> -// if (!hasFocus) { -// savePassengerData(adapterPosition) -// } -// } -// binding.etDate.setOnFocusChangeListener { _, hasFocus -> -// if (!hasFocus) { -// savePassengerData(adapterPosition) -// } -// } -// } -// } -// } -// -// fun saveAllPassengerData() { -// for (i in 0 until itemCount) { -// savePassengerData(i) -// } -// } -// -// private fun savePassengerData(position: Int) { -// val itemView = recyclerView.findViewHolderForAdapterPosition(position)?.itemView -// val spinnerTitle = itemView?.findViewById(R.id.spinner_title) -// val etFullName = itemView?.findViewById(R.id.et_full_name) -// val etFamilyName = itemView?.findViewById(R.id.et_family_name) -// val etDateOfBirth = itemView?.findViewById(R.id.et_date) -// val nationality = itemView?.findViewById(R.id.et_country) -// val idOrPassport = itemView?.findViewById(R.id.et_passport) -// val etValidUntil = itemView?.findViewById(R.id.et_valid_until) -// if (spinnerTitle != null && etFullName != null && etDateOfBirth != null && nationality != null && idOrPassport != null && etValidUntil != null) { -// val passengerData = -// PassengerData( -// spinnerTitle.selectedItem.toString(), -// etFullName.text.toString(), -// etFamilyName?.text.toString(), -// etDateOfBirth.text.toString(), -// nationality.text.toString(), -// idOrPassport.text.toString(), -// etValidUntil.text.toString(), -// ) -// if (position < passengerDataList.size) { -// passengerDataList[position] = passengerData -// } else { -// passengerDataList.add(passengerData) -// } -// } -// } -// -// fun getPassengerDataList(): List { -// saveAllPassengerData() -// return passengerDataList -// } -// } diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerItem.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerItem.kt index 72c35b5..44a39c5 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerItem.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/passengerbiodata/adapter/PassengerItem.kt @@ -1,8 +1,10 @@ package com.kom.skyfly.presentation.checkout.passengerbiodata.adapter +import android.util.Log import android.view.View import android.widget.ArrayAdapter import android.widget.EditText +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import com.kom.skyfly.R import com.kom.skyfly.data.model.passenger.PassengerData @@ -43,7 +45,32 @@ class PassengerItem( scHaveFamilyName.setOnCheckedChangeListener { _, isChecked -> tvFamilyName.isVisible = isChecked tilFamilyName.isVisible = isChecked + if (isChecked) { + val trackDrawable = scHaveFamilyName.trackDrawable + trackDrawable?.setTint( + ContextCompat.getColor( + viewBinding.root.context, + R.color.md_theme_primaryFixed_mediumContrast, + ), + ) + } else { + val trackDrawable = scHaveFamilyName.trackDrawable + trackDrawable?.setTint( + ContextCompat.getColor( + viewBinding.root.context, + R.color.grey, + ), + ) + } } + if (passengerTypeLabel == "CHILD" || passengerTypeLabel == "INFRANT") { + tilPassport.isVisible = false + tvPassport.isVisible = false + tvValidUntil.isVisible = false + ivCalendarValidUntil.isVisible = false + tilValidUntil.isVisible = false + } + Log.d("passengerType", "bind: $passengerTypeLabel") etDate.setOnClickListener { onDateClick(etDate) } etValidUntil.setOnClickListener { onValidUntilClick(etValidUntil) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/checkout/payment/PaymentActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/checkout/payment/PaymentActivity.kt index 01477b6..7808318 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/checkout/payment/PaymentActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/checkout/payment/PaymentActivity.kt @@ -1,6 +1,6 @@ package com.kom.skyfly.presentation.checkout.payment -import BottomSheetsIssueTicket +import IssueTicketBottomSheets import android.annotation.SuppressLint import android.app.ProgressDialog import android.content.Intent @@ -14,6 +14,7 @@ import android.webkit.WebView import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.AppCompatButton +import com.kom.skyfly.R import com.kom.skyfly.databinding.ActivityPaymentBinding class PaymentActivity : AppCompatActivity() { @@ -28,7 +29,8 @@ class PaymentActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - btnCloseSnap = binding.btnClose + setHeaderTitle() + btnCloseSnap = binding.btnNext btnCloseSnap!!.setOnClickListener( View.OnClickListener { view: View? -> transactionId?.let { openIssueTicketBottomSheets(it, adult, children, baby) } @@ -45,6 +47,13 @@ class PaymentActivity : AppCompatActivity() { } } + private fun setHeaderTitle() { + binding.layoutHeader.tvTitleHeader.setText(R.string.text_payment) + binding.layoutHeader.ivBack.setOnClickListener { + onBackPressed() + } + } + @SuppressLint("SetJavaScriptEnabled") private fun openUrlFromWebView(url: String?) { val webView: WebView = binding.wvPayment @@ -121,7 +130,7 @@ class PaymentActivity : AppCompatActivity() { ) { if (!supportFragmentManager.isStateSaved) { val bottomSheetFragment = - BottomSheetsIssueTicket.newInstance(transactionId, adult, child, baby) + IssueTicketBottomSheets.newInstance(transactionId, adult, child, baby) bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag) } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/HistoryFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/history/HistoryFragment.kt index ba94acf..12844c7 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/HistoryFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/HistoryFragment.kt @@ -1,42 +1,49 @@ package com.kom.skyfly.presentation.history +import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.lifecycle.map import androidx.recyclerview.widget.LinearLayoutManager import com.kom.skyfly.R -import com.kom.skyfly.data.model.history.Data +import com.kom.skyfly.core.BaseActivity +import com.kom.skyfly.data.datasource.history.FlightCodeListener import com.kom.skyfly.databinding.FragmentHistoryBinding +import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.presentation.history.filterflighthistory.CalendarView import com.kom.skyfly.presentation.history.flightdetailhistory.FlightDetailHistoryActivity import com.kom.skyfly.presentation.history.searchflighthistory.SearchFlightHistoryFragment import com.kom.skyfly.presentation.history.viewitems.DataItem import com.kom.skyfly.presentation.history.viewitems.HeaderItem +import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.Section import org.koin.androidx.viewmodel.ext.android.viewModel +import java.time.LocalDate -class HistoryFragment : Fragment() { +class HistoryFragment : Fragment(), FlightCodeListener, CalendarView.DateSelectedListener { private lateinit var binding: FragmentHistoryBinding + private val adapter: GroupieAdapter by lazy { GroupieAdapter() } + private val limit: Int = 5000 + private var startDate: String? = null + private var endDate: String? = null - private val adapter: GroupieAdapter by lazy { - GroupieAdapter() - } - - private val historyViewModel: HistoryViewModel by viewModel() + private val homeViewModel: HistoryViewModel by viewModel() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { - binding = FragmentHistoryBinding.inflate(layoutInflater, container, false) + binding = FragmentHistoryBinding.inflate(inflater, container, false) return binding.root } @@ -45,88 +52,171 @@ class HistoryFragment : Fragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - getHistoryData() - setRecyclerView() setClickListener() + setupRecyclerView() + setupSearchButton() + getHistoryData(limit, null, null, null) + } + + private fun setupRecyclerView() { + binding.rvPage.layoutManager = LinearLayoutManager(requireContext()) + binding.rvPage.adapter = adapter + } + + private fun setupSearchButton() { + binding.layoutHeaderOrderHistory.ivSearch.setOnClickListener { + showSearchFlightHistoryFragment() + } + } + + private fun showSearchFlightHistoryFragment() { + val searchFlightHistoryFragment = SearchFlightHistoryFragment() + searchFlightHistoryFragment.setFlightCodeListener(this) + searchFlightHistoryFragment.show(parentFragmentManager, searchFlightHistoryFragment.tag) } - private fun getHistoryData() { - historyViewModel.getHistoryData().observe(viewLifecycleOwner) { result -> - result.proceedWhen( - doOnLoading = { - binding.layoutState.root.isVisible = true - binding.shimmerHistory.isVisible = true - binding.layoutState.tvError.isVisible = false - binding.rvPage.isVisible = false - }, - doOnSuccess = { data -> - binding.layoutState.root.isVisible = false - binding.shimmerHistory.isVisible = false - binding.layoutState.tvError.isVisible = false - binding.rvPage.isVisible = true - - data.payload?.forEach { sectionedDate -> - val section = - Section().apply { - setHeader( - HeaderItem(sectionedDate.date) { date -> - Toast.makeText( - requireContext(), - "Header Clicked : $date", - Toast.LENGTH_SHORT, - ).show() - }, - ) - val dataItems = - sectionedDate.data.map { data -> - DataItem(data) { clickedData -> - navigateToDetail(clickedData) + override fun onFlightCodeEntered(flightCode: String) { + getHistoryData(limit, startDate, endDate, flightCode) + } + + private fun getHistoryData( + limit: Int?, + startDate: String?, + endDate: String?, + flightCode: String?, + ) { + homeViewModel.getHistoryData(limit, startDate, endDate, flightCode) + .observe(viewLifecycleOwner) { result -> + result.proceedWhen( + doOnLoading = { + binding.main.isRefreshing = true + binding.shimmerHistory.isVisible = true + binding.rvPage.isVisible = false + binding.csvHistory.isVisible = false + }, + doOnSuccess = { data -> + binding.main.isRefreshing = false + binding.shimmerHistory.isVisible = false + binding.rvPage.isVisible = true + binding.csvHistory.setState(ContentState.SUCCESS) + + if (result.payload?.data == null || result.payload.data.isEmpty()) { + binding.csvHistory.setState( + ContentState.EMPTY, + getString(R.string.text_history_data_empty), + ) + } + + adapter.clear() + + data.payload?.let { sectionedDate -> + sectionedDate.data.forEach { itemsHistoryDomain -> + val section = + Section().apply { + setHeader( + HeaderItem(itemsHistoryDomain.date) { date -> + Toast.makeText( + requireContext(), + "Header Clicked: $date", + Toast.LENGTH_SHORT, + ).show() + }, + ) + + val uniqueItemsMap = LinkedHashMap() + val sortedTransactions = + itemsHistoryDomain.transactions.sortedByDescending { it.id } + + sortedTransactions.forEach { transaction -> + val dataItem = + DataItem(transaction) { clickedData, transactionId -> + navigateToDetail(transactionId) + } + + val key = transaction.id + if (!uniqueItemsMap.containsKey(key)) { + uniqueItemsMap[key] = dataItem + } + } + + uniqueItemsMap.forEach { + add(it.value) } } - addAll(dataItems) + adapter.add(section) } - adapter.add(section) - } - }, - doOnError = { error -> - Toast.makeText(requireContext(), "Error: ${error.message}", Toast.LENGTH_SHORT) - .show() - }, - doOnEmpty = { - binding.layoutState.root.isVisible = true - binding.shimmerHistory.isVisible = false - binding.layoutState.ivError.isVisible = true - binding.layoutState.tvTitleError.isVisible = true - binding.layoutState.tvTitleError.text = getString(R.string.text_history_data_empty) - binding.layoutState.tvError.isVisible = true - binding.layoutState.tvError.text = getString(R.string.text_havent_made_a_booking) - binding.rvPage.isVisible = false - }, - ) - } - } + } + }, + doOnError = { error -> + binding.main.isRefreshing = false + binding.shimmerHistory.isVisible = false - private fun setRecyclerView() { - binding.rvPage.apply { - layoutManager = LinearLayoutManager(requireContext()) - adapter = this@HistoryFragment.adapter - } + if (error.exception is NoInternetException) { + binding.csvHistory.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + (activity as BaseActivity).errorHandler(error.exception) + binding.csvHistory.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + (activity as BaseActivity).errorHandler(error.exception) + binding.csvHistory.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) + } else { + binding.csvHistory.setState(ContentState.ERROR_GENERAL) + } + Log.e("HistoryFragment", "Error: ${error.exception?.message}") + }, + doOnEmpty = { + binding.main.isRefreshing = false + binding.shimmerHistory.isVisible = false + binding.csvHistory.setState( + ContentState.EMPTY, + getString(R.string.text_history_data_empty), + ) + }, + ) + } } private fun setClickListener() { binding.clFilter.setOnClickListener { val calendarView = CalendarView() + calendarView.setDateSelectedListener(this) calendarView.show(parentFragmentManager, calendarView.tag) } binding.layoutHeaderOrderHistory.ivSearch.setOnClickListener { val searchHistory = SearchFlightHistoryFragment() searchHistory.show(parentFragmentManager, searchHistory.tag) } - binding.rvPage.setOnClickListener { + binding.main.setOnRefreshListener { + startDate = null + endDate = null + getHistoryData(limit, null, null, null) } } - private fun navigateToDetail(item: Data) { - FlightDetailHistoryActivity.startActivity(requireContext(), item) + private fun navigateToDetail(transactionId: String?) { + val intent = + Intent(requireContext(), FlightDetailHistoryActivity::class.java).apply { + putExtra("EXTRAS_TRANSACTION_ID", transactionId) + } + startActivity(intent) + } + + override fun onDateSelected( + startDate: LocalDate?, + endDate: LocalDate?, + ) { + this.startDate = startDate?.toString() + this.endDate = endDate?.toString() + getHistoryData(limit, this.startDate, this.endDate, null) } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/HistoryViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/history/HistoryViewModel.kt index aadd831..1997cc3 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/HistoryViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/HistoryViewModel.kt @@ -8,5 +8,11 @@ import kotlinx.coroutines.Dispatchers class HistoryViewModel( private val historyRepository: HistoryRepository, ) : ViewModel() { - fun getHistoryData() = historyRepository.getHistoryData().asLiveData(Dispatchers.IO) + fun getHistoryData( + limit: Int?, + startDate: String?, + endDate: String?, + flightCode: String?, + ) = historyRepository.getHistoryData(limit, startDate, endDate, flightCode) + .asLiveData(Dispatchers.IO) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/filterflighthistory/CalendarView.kt b/app/src/main/java/com/kom/skyfly/presentation/history/filterflighthistory/CalendarView.kt index 26162ad..63d40b5 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/filterflighthistory/CalendarView.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/filterflighthistory/CalendarView.kt @@ -34,6 +34,19 @@ class CalendarView : BottomSheetDialogFragment() { private val dateFormatter = DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy", Locale("id", "ID")) + fun setDateSelectedListener(listener: DateSelectedListener) { + dateSelectedListener = listener + } + + private var dateSelectedListener: DateSelectedListener? = null + + interface DateSelectedListener { + fun onDateSelected( + startDate: LocalDate?, + endDate: LocalDate?, + ) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -50,10 +63,7 @@ class CalendarView : BottomSheetDialogFragment() { super.onViewCreated(view, savedInstanceState) setOnClickListener() - val maxPeekHeight = - resources.getDimensionPixelSize( - R.dimen.max_bottom_sheet_height, - ) + val maxPeekHeight = resources.getDimensionPixelSize(R.dimen.max_bottom_sheet_height) setBottomSheetMaxHeight(maxPeekHeight) val daysOfWeek = DayOfWeek.values() @@ -110,7 +120,6 @@ class CalendarView : BottomSheetDialogFragment() { textView.visibility = View.INVISIBLE } } - textView.alpha = if (data.position == DayPosition.MonthDate) 1f else 0.3f } } @@ -151,15 +160,22 @@ class CalendarView : BottomSheetDialogFragment() { binding.ivPreviousMonth.setOnClickListener { binding.calendarView.smoothScrollToMonth( - binding.calendarView.findFirstVisibleMonth()?.yearMonth?.minusMonths(1) ?: YearMonth.now().minusMonths(1), + binding.calendarView.findFirstVisibleMonth()?.yearMonth?.minusMonths(1) + ?: YearMonth.now().minusMonths(1), ) } binding.ivNextMonth.setOnClickListener { binding.calendarView.smoothScrollToMonth( - binding.calendarView.findFirstVisibleMonth()?.yearMonth?.plusMonths(1) ?: YearMonth.now().plusMonths(1), + binding.calendarView.findFirstVisibleMonth()?.yearMonth?.plusMonths(1) + ?: YearMonth.now().plusMonths(1), ) } + + binding.btnSave.setOnClickListener { + dateSelectedListener?.onDateSelected(startDate, endDate) + dismiss() + } } private fun setOnClickListener() { @@ -191,7 +207,7 @@ class CalendarView : BottomSheetDialogFragment() { } inner class DayViewContainer(view: View) : ViewContainer(view) { - val textView = view.findViewById(R.id.calendarDayText) + val textView: TextView = view.findViewById(R.id.calendarDayText) lateinit var day: CalendarDay init { @@ -212,6 +228,6 @@ class CalendarView : BottomSheetDialogFragment() { } inner class MonthViewContainer(view: View) : ViewContainer(view) { - val titlesContainer = view as ViewGroup + val titlesContainer: ViewGroup = view as ViewGroup } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryActivity.kt index f1380e4..9aa8d01 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryActivity.kt @@ -1,90 +1,316 @@ package com.kom.skyfly.presentation.history.flightdetailhistory -import android.content.Context -import android.content.Intent +import android.annotation.SuppressLint import android.content.res.ColorStateList import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import coil.load import com.kom.skyfly.R -import com.kom.skyfly.data.model.history.Data +import com.kom.skyfly.core.BaseActivity +import com.kom.skyfly.data.model.transaction.detail.TransactionDetailResponses +import com.kom.skyfly.data.model.transaction.paymentstatus.ItemsPaymentStatus import com.kom.skyfly.databinding.ActivityFlightDetailHistoryBinding +import com.kom.skyfly.presentation.common.views.ContentState +import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException +import com.kom.skyfly.utils.formatToRupiah +import com.kom.skyfly.utils.proceedWhen +import es.dmoral.toasty.Toasty import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -class FlightDetailHistoryActivity : AppCompatActivity() { - // private lateinit var currentHistory: HistoryDetail +class FlightDetailHistoryActivity : BaseActivity() { private val binding: ActivityFlightDetailHistoryBinding by lazy { ActivityFlightDetailHistoryBinding.inflate(layoutInflater) } - private val detailHistoryViewModel: FlightDetailHistoryViewModel by viewModel { - parametersOf(intent.extras) - } + private val detailHistoryViewModel: FlightDetailHistoryViewModel by viewModel() + private var transactionIdFromExtras: String? = null + private var historyDetails: TransactionDetailResponses? = null + private var vaNumbers: ItemsPaymentStatus? = null + private var orderId: String = "" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - -// val historyId = intent.getStringExtra(EXTRAS_MOVIE) -// if (historyId != null) { -// setupBind(historyId) -// } else { -// // Handle jika bookingCode tidak tersedia -// } - detailHistoryViewModel.history?.let { setupBind(it) } - + transactionIdFromExtras = intent.getStringExtra("EXTRAS_TRANSACTION_ID") setOnClickListeners() + observeData(transactionIdFromExtras) } private fun setOnClickListeners() { binding.ivBack.setOnClickListener { onBackPressed() } + binding.main.setOnRefreshListener { + observeData(transactionIdFromExtras) + } + binding.btnCancelTransaction.setOnClickListener { + confirmCancelTransaction() + } } - companion object { - const val EXTRAS_HISTORY = "EXTRAS_HISTORY" + private fun confirmCancelTransaction() { + val dialog = + AlertDialog.Builder(this) + .setMessage(getString(R.string.text_are_you_sure_you_want_to_cancel_transaction)) + .setPositiveButton( + getString(R.string.text_yes), + ) { dialog, id -> + doCancelTransaction(orderId) + } + .setNegativeButton( + getString(R.string.text_no), + ) { dialog, id -> + }.create() + dialog.show() + } - fun startActivity( - context: Context, - data: Data, - ) { - val intent = Intent(context, FlightDetailHistoryActivity::class.java) - intent.putExtra(EXTRAS_HISTORY, data) - context.startActivity(intent) + private fun doCancelTransaction(orderId: String) { + detailHistoryViewModel.cancelTransaction(orderId).observe(this) { cancelResult -> + cancelResult.proceedWhen( + doOnSuccess = { + binding.pbLoading.isVisible = false + Toasty.success(this, "Transaction Cancelled!", Toast.LENGTH_SHORT).show() + observeData(transactionIdFromExtras) + }, + doOnLoading = { + binding.btnCancelTransaction.isEnabled = false + binding.pbLoading.isVisible = true + }, + doOnError = { error -> + if (error.exception is NoInternetException) { + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + errorHandler(error.exception) + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + errorHandler(error.exception) + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) + } else { + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_GENERAL, + getString(R.string.text_something_went_wrong), + ) + } + }, + ) } } -// private fun observeData(historyId: String) { -// detailHistoryViewModel.getDetailHistoryById(historyId).observe(this) { result -> -// result.proceedWhen( -// doOnSuccess = { -// it.payload?.let { historyDetail -> -// currentHistory = historyDetail -// setupBind(historyDetail) -// } -// }, -// ) -// } -// } - - private fun setupBind(historyDetail: Data) { + private fun observePaymentStatus(id: String?) { + if (id != null) { + detailHistoryViewModel.getPaymentStatus(id) + .observe(this) { resultStatus -> + resultStatus.proceedWhen( + doOnSuccess = { + vaNumbers = resultStatus.payload + setupBind(historyDetails, vaNumbers) + }, + doOnError = { error -> + vaNumbers = null + }, + doOnEmpty = { + vaNumbers = null + }, + ) + } + } + } + + private fun observeData(id: String?) { + if (id != null) { + detailHistoryViewModel.getHistoryById(id).observe(this) { result -> + result.proceedWhen( + doOnSuccess = { + binding.main.isRefreshing = false + binding.shimmerDetailHistory.isVisible = false + binding.svFlightDetails.isVisible = true + historyDetails = it.payload + orderId = it.payload?.data?.orderId.toString() + observePaymentStatus(orderId) + if (vaNumbers == null) { + setupBind(historyDetails, null) + } + }, + doOnLoading = { + binding.shimmerDetailHistory.isVisible = true + binding.svFlightDetails.isVisible = false + binding.btnCancelTransaction.isEnabled = false + binding.main.isRefreshing = true + }, + doOnError = { error -> + binding.main.isRefreshing = false + if (error.exception is NoInternetException) { + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + errorHandler(error.exception) + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + errorHandler(error.exception) + binding.csvFlightDetailsHistory.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) + } else { + binding.csvFlightDetailsHistory.setState(ContentState.ERROR_GENERAL) + } + }, + doOnEmpty = { + binding.main.isRefreshing = false + binding.shimmerDetailHistory.isVisible = false + getString(R.string.text_havent_made_a_booking) + binding.svFlightDetails.isVisible = false + binding.btnCancelTransaction.isVisible = false + }, + ) + } + } + } + + @SuppressLint("StringFormatMatches") + private fun setupBind( + historyDetail: TransactionDetailResponses?, + itemPaymentStatus: ItemsPaymentStatus?, + ) { + val transactionDetails = historyDetail?.data?.transactionDetails + val paymentStatus = historyDetail?.data?.status + + var adultCount = 0 + var childCount = 0 + var babyCount = 0 + var totalPricePassengerAdult = 0 + var totalPricePassengerChild = 0 + var totalPricePassengerBaby = 0 + var airlineImage: String? = null + historyDetail?.data?.transactionDetails?.map { + airlineImage = it?.flight?.airline?.image + } + + transactionDetails?.forEach { detail -> + when (detail?.passengerCategory) { + "ADULT" -> { + adultCount++ + totalPricePassengerAdult += detail.totalPrice + } + + "CHILD" -> { + childCount++ + totalPricePassengerChild += detail.totalPrice + } + + "INFRANT" -> { + babyCount++ + totalPricePassengerBaby += detail.totalPrice + } + } + } + binding.layoutFlightDetails.tvTitlePassenger.text = getString(R.string.text_empty) + binding.layoutFlightDetails.tvCitizenship.text = getString(R.string.text_empty) + with(binding.layoutFlightDetails) { - tvPaymentStatus.text = historyDetail.status - tvDetailDepartureDate.text = historyDetail.departureDate - tvDetailDepartureTime.text = historyDetail.departureTime - tvDetailArrivalDate.text = historyDetail.arrivalDate - tvDetailArrivalTime.text = historyDetail.arrivalTime - tvDetailBookingCode.text = historyDetail.bookingCode - tvDetailClass.text = historyDetail.flightClass - tvTotalPrice.text = historyDetail.total - - when (historyDetail.status) { - "Unpaid" -> { - tvPaymentStatus.background = - ContextCompat.getDrawable(tvPaymentStatus.context, R.drawable.btn_rounded) + ivAirline.load( + airlineImage, + ) { + error(R.drawable.img_airline) + } + tvPaymentStatus.text = paymentStatus + tvDetailBookingCode.text = historyDetail?.data?.booking?.code + tvDetailDepartureDate.text = + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.departure?.date + tvDetailDepartureTime.text = + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.departure?.time + tvDetailDepartureAirport.text = + "${historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.departureAirport?.name} - ${historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.airline?.terminal}" + tvDetailAirline.text = + getString( + R.string.tv_strip, + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.airline?.name, + ) + tvDetailClass.text = historyDetail?.data?.transactionDetails?.firstOrNull()?.seat?.type + tvDetailFlightNumber.text = + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.code + tvDetailArrivalDate.text = + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.arrival?.date + tvDetailArrivalTime.text = + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.arrival?.time + tvDetailDestinationAirport.text = + historyDetail?.data?.transactionDetails?.firstOrNull()?.flight?.destinationAirport?.name + tvTotalPrice.text = historyDetail?.data?.totalPrice?.formatToRupiah().toString() + tvTax.text = historyDetail?.data?.tax?.formatToRupiah().toString() + + if (adultCount > 0) { + tvTotalByAgeGroupAdult.text = getString(R.string.text_adult, adultCount) + tvTotalPriceByAgeGroupAdult.text = + totalPricePassengerAdult.formatToRupiah().toString() + tvTotalByAgeGroupAdult.isVisible = true + tvTotalPriceByAgeGroupAdult.isVisible = true + } else { + tvTotalByAgeGroupAdult.isVisible = false + tvTotalPriceByAgeGroupAdult.isVisible = false + } + + if (childCount > 0) { + tvTotalByAgeGroupChild.text = getString(R.string.text_child, childCount) + tvTotalPriceByAgeGroupChild.text = + totalPricePassengerChild.formatToRupiah().toString() + tvTotalByAgeGroupChild.isVisible = true + tvTotalPriceByAgeGroupChild.isVisible = true + } else { + tvTotalByAgeGroupChild.isVisible = false + tvTotalPriceByAgeGroupChild.isVisible = false + } + + if (babyCount > 0) { + tvTotalByAgeGroupBaby.text = getString(R.string.text_baby, babyCount) + tvTotalPriceByAgeGroupBaby.text = + totalPricePassengerBaby.formatToRupiah().toString() + tvTotalByAgeGroupBaby.isVisible = true + tvTotalPriceByAgeGroupBaby.isVisible = true + } else { + tvTotalByAgeGroupBaby.isVisible = false + tvTotalPriceByAgeGroupBaby.isVisible = false + } + + transactionDetails?.forEachIndexed { index, detail -> + val passengerName = detail?.name ?: "Unknown" + val citizenship = detail?.citizenship ?: "Unknown" + + tvTitlePassenger.append( + "Passenger ${index + 1} : $passengerName\n" + + "Citizenship : $citizenship\n", + ) + } + + when { + paymentStatus.equals("pending", ignoreCase = true) -> { + tvPaymentStatus.setText(R.string.text_unpaid) + tvVaNumber.text = + itemPaymentStatus?.vaNumbers?.joinToString { it.vaNumber.toString() } + ?: "N/A" + + tvPaymentCodeTitle.isVisible = true + tvVaNumber.isVisible = true tvPaymentStatus.backgroundTintList = ColorStateList.valueOf( ContextCompat.getColor( @@ -94,32 +320,43 @@ class FlightDetailHistoryActivity : AppCompatActivity() { ) } - "Cancelled" -> { - tvPaymentStatus.background = - ContextCompat.getDrawable(tvPaymentStatus.context, R.drawable.btn_rounded) + paymentStatus.equals("settlement", ignoreCase = true) -> { + tvPaymentStatus.setText(R.string.text_paid) tvPaymentStatus.backgroundTintList = ColorStateList.valueOf( ContextCompat.getColor( tvPaymentStatus.context, - R.color.darkGrey, + R.color.green, ), ) } - "Paid" -> { - tvPaymentStatus.background = - ContextCompat.getDrawable(tvPaymentStatus.context, R.drawable.btn_rounded) + else -> { + tvPaymentStatus.setText(R.string.text_cancelled) tvPaymentStatus.backgroundTintList = ColorStateList.valueOf( ContextCompat.getColor( tvPaymentStatus.context, - R.color.green, + R.color.darkGrey, ), ) } + } - else -> { - tvPaymentStatus.background = null + when { + paymentStatus.equals("pending", ignoreCase = true) -> { + binding.btnCancelTransaction.text = getString(R.string.text_cancel_transaction) + binding.btnCancelTransaction.isEnabled = true + } + + paymentStatus.equals("settlement", ignoreCase = true) -> { + binding.btnCancelTransaction.text = getString(R.string.text_payment_complete) + binding.btnCancelTransaction.isEnabled = false + } + + paymentStatus.equals("expired", ignoreCase = true) -> { + binding.btnCancelTransaction.text = getString(R.string.text_payment_cancelled) + binding.btnCancelTransaction.isEnabled = false } } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryViewModel.kt index 3af7269..743a72b 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/flightdetailhistory/FlightDetailHistoryViewModel.kt @@ -1,15 +1,23 @@ package com.kom.skyfly.presentation.history.flightdetailhistory -import android.os.Bundle import androidx.lifecycle.ViewModel -import com.kom.skyfly.data.model.history.Data -import com.kom.skyfly.data.repository.history.HistoryRepository +import androidx.lifecycle.asLiveData +import com.kom.skyfly.data.repository.transaction.TransactionRepository +import kotlinx.coroutines.Dispatchers class FlightDetailHistoryViewModel( - private val extras: Bundle?, - private val historyRepository: HistoryRepository, + private val transactionRepository: TransactionRepository, ) : ViewModel() { - val history = extras?.getParcelable(FlightDetailHistoryActivity.EXTRAS_HISTORY) + fun getHistoryById(id: String) = + transactionRepository + .getTransactionById(id) + .asLiveData(Dispatchers.IO) -// fun getDetailHistoryById(id: String) = historyRepository.getDetailHistoryById(id.toInt()).asLiveData(Dispatchers.IO) + fun getPaymentStatus(id: String) = + transactionRepository.getPaymentStatus(id) + .asLiveData(Dispatchers.IO) + + fun cancelTransaction(id: String) = + transactionRepository.cancelTransaction(id) + .asLiveData(Dispatchers.IO) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryFragment.kt index 4d98c0a..2633b53 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryFragment.kt @@ -13,24 +13,23 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.kom.skyfly.R +import com.kom.skyfly.data.datasource.history.FlightCodeListener import com.kom.skyfly.data.model.searchhistory.SearchHistory import com.kom.skyfly.databinding.FragmentSearchFlightHistoryBinding import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.presentation.history.searchflighthistory.adapter.SearchHistoryListAdapter import com.kom.skyfly.presentation.history.searchflighthistory.adapter.SearchHistoryListener -import com.kom.skyfly.utils.hideKeyboard import com.kom.skyfly.utils.proceedWhen import org.koin.androidx.viewmodel.ext.android.viewModel class SearchFlightHistoryFragment : BottomSheetDialogFragment() { private lateinit var binding: FragmentSearchFlightHistoryBinding - private val searchFlightHistoryViewModel: SearchFlightHistoryViewModel by viewModel() - + private var flightCodeListener: FlightCodeListener? = null private val adapter: SearchHistoryListAdapter by lazy { SearchHistoryListAdapter( itemClick = { searchHistory -> - // Handle item click here if needed + binding.layoutSearchBar.etSearch.setText(searchHistory.searchHistory) }, searchHistoryListener = object : SearchHistoryListener { @@ -41,12 +40,16 @@ class SearchFlightHistoryFragment : BottomSheetDialogFragment() { ) } + fun setFlightCodeListener(listener: FlightCodeListener) { + flightCodeListener = listener + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View { - binding = FragmentSearchFlightHistoryBinding.inflate(layoutInflater, container, false) + binding = FragmentSearchFlightHistoryBinding.inflate(inflater, container, false) return binding.root } @@ -59,10 +62,7 @@ class SearchFlightHistoryFragment : BottomSheetDialogFragment() { getSearchHistoryData() setList() - val maxPeekHeight = - resources.getDimensionPixelSize( - R.dimen.max_bottom_sheet_height, - ) + val maxPeekHeight = resources.getDimensionPixelSize(R.dimen.max_bottom_sheet_height) setBottomSheetMaxHeight(maxPeekHeight) } @@ -72,10 +72,8 @@ class SearchFlightHistoryFragment : BottomSheetDialogFragment() { doOnSuccess = { binding.rvPage.isVisible = true binding.csvSearchHistory.setState(ContentState.SUCCESS) - result.payload.let { - if (it != null) { - adapter.submitData(it) - } + result.payload?.let { + adapter.submitData(it) } }, doOnError = { @@ -113,28 +111,22 @@ class SearchFlightHistoryFragment : BottomSheetDialogFragment() { binding.layoutSearchBar.etSearch.setOnEditorActionListener { v, actionId, event -> if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE || - event != null && - event.action == KeyEvent.ACTION_DOWN && - event.keyCode == KeyEvent.KEYCODE_ENTER + event != null && event.action == KeyEvent.ACTION_DOWN && event.keyCode == KeyEvent.KEYCODE_ENTER ) { val searchHistory = binding.layoutSearchBar.etSearch.text.toString() - observeData("39483943tfeff", searchHistory) - binding.layoutSearchBar.etSearch.clearFocus() - hideKeyboard() + + flightCodeListener?.onFlightCodeEntered(searchHistory) + observeData("gejefg", searchHistory) + dismiss() return@setOnEditorActionListener true } - return@setOnEditorActionListener false - } - binding.tvDeleteRecentSearches.setOnClickListener { - searchFlightHistoryViewModel.deleteAllSearchHistory() - Toast.makeText(requireContext(), "deleteall", Toast.LENGTH_SHORT).show() + false } } private fun setList() { - val layoutManager = LinearLayoutManager(requireContext()) - binding.rvPage.layoutManager = layoutManager - binding.rvPage.adapter = this@SearchFlightHistoryFragment.adapter + binding.rvPage.layoutManager = LinearLayoutManager(requireContext()) + binding.rvPage.adapter = adapter } private fun observeData( diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryViewModel.kt index 2e25f97..6cf3ed2 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/searchflighthistory/SearchFlightHistoryViewModel.kt @@ -11,10 +11,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch -/** -Written by Komang Yuda Saputra -Github : https://github.com/YudaSaputraa - **/ class SearchFlightHistoryViewModel(private val searchHistoryRepository: SearchHistoryRepository) : ViewModel() { fun getAllSearchHistory() = searchHistoryRepository.getUserSearchHistory().asLiveData(Dispatchers.IO) diff --git a/app/src/main/java/com/kom/skyfly/presentation/history/viewitems/Items.kt b/app/src/main/java/com/kom/skyfly/presentation/history/viewitems/Items.kt index fdc3c51..2c341aa 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/history/viewitems/Items.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/history/viewitems/Items.kt @@ -4,13 +4,16 @@ import android.content.res.ColorStateList import android.view.View import androidx.core.content.ContextCompat import com.kom.skyfly.R -import com.kom.skyfly.data.model.history.Data +import com.kom.skyfly.data.model.history.new.TransactionDomain import com.kom.skyfly.databinding.ItemOrderHistoryBinding import com.kom.skyfly.databinding.ItemSectionHeaderHistoryBinding +import com.kom.skyfly.utils.formatToRupiah import com.xwray.groupie.viewbinding.BindableItem -class HeaderItem(private val title: String, private val onHeaderClick: (item: String) -> Unit) : - BindableItem() { +class HeaderItem( + private val title: String, + private val onHeaderClick: (item: String) -> Unit, +) : BindableItem() { override fun bind( viewBinding: ItemSectionHeaderHistoryBinding, position: Int, @@ -21,71 +24,90 @@ class HeaderItem(private val title: String, private val onHeaderClick: (item: St override fun getLayout(): Int = R.layout.item_section_header_history - override fun initializeViewBinding(view: View): ItemSectionHeaderHistoryBinding = ItemSectionHeaderHistoryBinding.bind(view) + override fun initializeViewBinding(view: View): ItemSectionHeaderHistoryBinding { + return ItemSectionHeaderHistoryBinding.bind(view) + } } -class DataItem(private val data: Data, private val onItemClick: (item: Data) -> Unit) : +class DataItem( + private val data: TransactionDomain, + private val onItemClick: (item: TransactionDomain, transactionId: String?) -> Unit, +) : BindableItem() { + var paymentStatus: String? = null + var departureCity: String? = null + var departureDate: String? = null + var departureTime: String? = null + var flightDuration: String? = null + var destinationCity: String? = null + var arrivalDate: String? = null + var arrivalTime: String? = null + var bookingCode: String? = null + var seatClass: String? = null + var totalPrice: String? = null + var transactionId: String? = null + override fun bind( viewBinding: ItemOrderHistoryBinding, position: Int, ) { + paymentStatus = data.status + departureCity = data.transactionDetail.first().flight.departureAirport.city + departureDate = data.transactionDetail.first().flight.departure.date + departureTime = data.transactionDetail.first().flight.departure.time + flightDuration = data.transactionDetail.first().flight.flightDuration + destinationCity = data.transactionDetail.first().flight.destinationAirport.city + arrivalDate = data.transactionDetail.first().flight.arrival.date + arrivalTime = data.transactionDetail.first().flight.arrival.time + bookingCode = data.booking.code + seatClass = data.transactionDetail.first().seat.type + totalPrice = data.totalPrice.formatToRupiah().toString() + transactionId = data.id + with(viewBinding) { - tvStatus.text = data.status - tvDepartureLocation.text = data.departureLocation - tvDepartureDate.text = data.departureDate - tvDepartureTime.text = data.departureTime - tvFlightDuration.text = data.flightDuration - tvDestination.text = data.destination - tvArrivalDate.text = data.arrivalDate - tvArrivalTime.text = data.arrivalTime - tvBookingCode.text = data.bookingCode - tvClass.text = data.flightClass - tvHistoryTotal.text = data.total + tvStatus.text = paymentStatus + tvDepartureLocation.text = departureCity + tvDepartureDate.text = departureDate + tvDepartureTime.text = departureTime + tvFlightDuration.text = flightDuration + tvDestination.text = destinationCity + tvArrivalDate.text = arrivalDate + tvArrivalTime.text = arrivalTime + tvBookingCode.text = bookingCode + tvClass.text = seatClass + tvHistoryTotal.text = totalPrice viewBinding.root.setOnClickListener { - onItemClick(data) + onItemClick(data, transactionId) } - when (data.status) { - "Unpaid" -> { - tvStatus.background = - ContextCompat.getDrawable(tvStatus.context, R.drawable.btn_rounded) - tvStatus.backgroundTintList = - ColorStateList.valueOf( - ContextCompat.getColor( - tvStatus.context, - R.color.red, - ), - ) - } - - "Cancelled" -> { - tvStatus.background = - ContextCompat.getDrawable(tvStatus.context, R.drawable.btn_rounded) - tvStatus.backgroundTintList = - ColorStateList.valueOf( - ContextCompat.getColor( - tvStatus.context, - R.color.darkGrey, - ), - ) - } - - "Paid" -> { - tvStatus.background = - ContextCompat.getDrawable(tvStatus.context, R.drawable.btn_rounded) - tvStatus.backgroundTintList = - ColorStateList.valueOf( - ContextCompat.getColor( - tvStatus.context, - R.color.green, - ), - ) - } - else -> { - tvStatus.background = null - } + if (paymentStatus.equals("pending", ignoreCase = true)) { + tvStatus.setText(R.string.text_unpaid) + tvStatus.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor( + tvStatus.context, + R.color.red, + ), + ) + } else if (paymentStatus.equals("settlement", ignoreCase = true)) { + tvStatus.setText(R.string.text_paid) + tvStatus.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor( + tvStatus.context, + R.color.green, + ), + ) + } else if (paymentStatus.equals("expire", ignoreCase = true) || paymentStatus.equals("cancel", ignoreCase = true)) { + tvStatus.setText(R.string.text_cancelled) + tvStatus.backgroundTintList = + ColorStateList.valueOf( + ContextCompat.getColor( + tvStatus.context, + R.color.darkGrey, + ), + ) } } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/HomeFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/home/HomeFragment.kt index edfdaf7..0669d0f 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/HomeFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/HomeFragment.kt @@ -1,5 +1,6 @@ package com.kom.skyfly.presentation.home +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -8,7 +9,7 @@ import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.kom.skyfly.R -import com.kom.skyfly.data.model.destinationfavorite.DestinationFavorite +import com.kom.skyfly.data.model.home.destination_favourite.DestinationFavourite import com.kom.skyfly.databinding.FragmentHomeBinding import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.presentation.home.adapter.DestinationFavoriteAdapter @@ -16,11 +17,11 @@ import com.kom.skyfly.presentation.home.calendar.HomeCalendarFragment import com.kom.skyfly.presentation.home.passenger.PassengerFragment import com.kom.skyfly.presentation.home.search.SearchFragment import com.kom.skyfly.presentation.home.search_result.SearchResultActivity +import com.kom.skyfly.presentation.home.seatclass.SeatClassFragment import com.kom.skyfly.presentation.main.MainViewModel import com.kom.skyfly.utils.NoInternetException import com.kom.skyfly.utils.proceedWhen import org.koin.androidx.viewmodel.ext.android.activityViewModel -import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.viewModel import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -31,9 +32,20 @@ class HomeFragment : Fragment() { private lateinit var binding: FragmentHomeBinding private val homeViewModel: HomeViewModel by viewModel() private val sharedViewModel: MainViewModel by activityViewModel() - private val destinationAdapter: DestinationFavoriteAdapter by lazy { DestinationFavoriteAdapter {} } private var source: String = "" private var dest: String = "" + private var departureAirport: String? = null + private var arrivalAirport: String? = null + private val destinationAdapter: DestinationFavoriteAdapter by lazy { + DestinationFavoriteAdapter { item -> + item.let { + binding.layoutSelectDestination.tvStartFrom.text = it.departureCity + binding.layoutSelectDestination.tvEndDestination.text = it.arrivalCity + departureAirport = it.departureCity + arrivalAirport = it.arrivalCity + } + } + } override fun onCreateView( inflater: LayoutInflater, @@ -57,50 +69,57 @@ class HomeFragment : Fragment() { getDestinationFavoriteData() } + @SuppressLint("SetTextI18n") private fun observeDataDestination() { sharedViewModel.sourceDestination.observe(viewLifecycleOwner) { destination -> destination?.let { if (sharedViewModel.isStartDestination!!) { binding.layoutSelectDestination.tvStartFrom.text = it.city source = binding.layoutSelectDestination.tvStartFrom.text.toString() + departureAirport = it.city } else { binding.layoutSelectDestination.tvEndDestination.text = it.city dest = binding.layoutSelectDestination.tvEndDestination.text.toString() + arrivalAirport = it.city } } + validateFields() } sharedViewModel.startTime.observe(viewLifecycleOwner) { startTIme -> startTIme?.let { binding.tvDeparture.text = it } + validateFields() } sharedViewModel.returnTime.observe(viewLifecycleOwner) { returnTime -> returnTime?.let { binding.tvReturn.text = it } + validateFields() } sharedViewModel.passengerCountLiveData.observe(viewLifecycleOwner) { totalPassenger -> totalPassenger?.let { - binding.tvPassengers.text = it.toString() + binding.tvPassengers.text = "$it Passengers" } + validateFields() + } + sharedViewModel.seatClass.observe(viewLifecycleOwner) { seatClass -> + seatClass?.let { + binding.tvSeats.text = it + } + validateFields() } } - fun convertDateFormat(inputDate: String): String? { - // Define the formatter for the input date format + private fun convertDateFormat(inputDate: String): String? { val inputFormatter = DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy", Locale("id", "ID")) - // Define the formatter for the output date format val outputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") return try { - // Parse the input date string to a LocalDate object val date = LocalDate.parse(inputDate, inputFormatter) - - // Format the LocalDate object to the output format date.format(outputFormatter) } catch (e: DateTimeParseException) { - // Handle the exception if the input date string is not in the expected format e.printStackTrace() null } @@ -132,15 +151,26 @@ class HomeFragment : Fragment() { passengerBottomSheet.show(parentFragmentManager, passengerBottomSheet.tag) } binding.btnSearchFlight.setOnClickListener { - val departureAirport = binding.layoutSelectDestination.tvStartFrom.text - val arrivalAirport = binding.layoutSelectDestination.tvEndDestination.text + val babyCount = sharedViewModel.passengerBabyCountLiveData + val adultCount = sharedViewModel.passengerAdultCountLiveData + val childCount = sharedViewModel.passengerChildCountLiveData + val seatClass = sharedViewModel.seatClass + val roundTrip = sharedViewModel.roundTrip + val returnDate = convertDateFormat(binding.tvReturn.text.toString()) val departureTime = convertDateFormat(binding.tvDeparture.text.toString()) val intent = Intent(requireContext(), SearchResultActivity::class.java).apply { putExtra("EXTRA_DEPARTURE_AIRPORT", departureAirport) putExtra("EXTRA_ARRIVAL_AIRPORT", arrivalAirport) + putExtra("EXTRA_RETURN_TIME", returnDate) putExtra("EXTRA_DEPARTURE_TIME", departureTime) + putExtra("EXTRA_BABY_COUNT", babyCount.value) + putExtra("EXTRA_ADULT_COUNT", adultCount.value) + putExtra("EXTRA_CHILD_COUNT", childCount.value) + putExtra("EXTRA_SEAT_CLASS", seatClass.value) + putExtra("EXTRA_TOTAL_PASSENGER", sharedViewModel.passengerCountLiveData.value) + putExtra("EXTRA_ROUND_TRIP", roundTrip.value) } startActivity(intent) } @@ -151,6 +181,24 @@ class HomeFragment : Fragment() { source = dest dest = temp } + binding.tvSeats.setOnClickListener { + val seatClassBottomSheet = SeatClassFragment() + seatClassBottomSheet.show(parentFragmentManager, seatClassBottomSheet.tag) + } + } + + private fun validateFields() { + val isSourceValid = binding.layoutSelectDestination.tvStartFrom.text.toString().isNotEmpty() + val isDestinationValid = + binding.layoutSelectDestination.tvEndDestination.text.toString().isNotEmpty() + val isDepartureTimeValid = binding.tvDeparture.text.toString().isNotEmpty() + val isReturnTimeValid = + !binding.tvReturn.isEnabled || binding.tvReturn.text.toString().isNotEmpty() + val isPassengerCountValid = sharedViewModel.passengerCountLiveData.value != null + val isSeatClassValid = sharedViewModel.seatClass.value != null + + binding.btnSearchFlight.isEnabled = + isSourceValid && isDestinationValid && isDepartureTimeValid && isReturnTimeValid && isPassengerCountValid && isSeatClassValid } private fun getDestinationFavoriteData() { @@ -187,18 +235,24 @@ class HomeFragment : Fragment() { private fun setRoundTrip() { binding.btnSwitchRoundtrip.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { + sharedViewModel.setRoundTrip(true) binding.tvReturn.isEnabled = true - binding.tvReturn.text = "Select Dates" - binding.tvReturn.setTextColor(getResources().getColor(R.color.md_theme_primary)) + binding.tvReturn.text = getString(R.string.text_select_dates) + val trackDrawable = binding.btnSwitchRoundtrip.trackDrawable + trackDrawable?.setTint(resources.getColor(R.color.md_theme_primaryFixed_mediumContrast)) + binding.tvReturn.setTextColor(resources.getColor(R.color.md_theme_primary)) } else { + sharedViewModel.setRoundTrip(false) binding.tvReturn.isEnabled = false - binding.tvReturn.text = "-" - binding.tvReturn.setTextColor(getResources().getColor(R.color.darkGrey)) + binding.tvReturn.text = getString(R.string.text_strips) + binding.tvReturn.setTextColor(resources.getColor(R.color.darkGrey)) + val trackDrawable = binding.btnSwitchRoundtrip.trackDrawable + trackDrawable?.setTint(resources.getColor(R.color.grey)) } } } - private fun bindDestinationFavoriteList(data: List) { + private fun bindDestinationFavoriteList(data: List) { destinationAdapter.submitData(data) } @@ -207,8 +261,4 @@ class HomeFragment : Fragment() { adapter = destinationAdapter } } - - companion object { - const val EXTRAS_DESTINATION = "EXTRAS_DESTINATION" - } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/HomeViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/home/HomeViewModel.kt index 32c6f49..242db14 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/HomeViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/HomeViewModel.kt @@ -2,8 +2,8 @@ package com.kom.skyfly.presentation.home import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData -import com.kom.skyfly.data.repository.destinationfavorite.DestinationFavoriteRepository import com.kom.skyfly.data.repository.home.airport.AirportRepository +import com.kom.skyfly.data.repository.home.destination_favourite.DestinationFavouriteRepository import com.kom.skyfly.data.repository.home.flight_ticket.FlightTicketRepository import com.kom.skyfly.data.repository.userpref.UserPrefRepository import kotlinx.coroutines.Dispatchers @@ -17,14 +17,14 @@ class HomeViewModel( private val userPrefRepository: UserPrefRepository, private val airportRepository: AirportRepository, private val flightRepository: FlightTicketRepository, - private val destinationFavoriteRepository: DestinationFavoriteRepository, + private val destinationFavoriteRepository: DestinationFavouriteRepository, ) : ViewModel() { // val destination = extras?.getParcelable(HomeFragment.EXTRAS_DESTINATION) fun setOnBoardingShow(isShown: Boolean) = userPrefRepository.setOnBoardingShow(isShown) fun getAllDestinationFavorite() = - destinationFavoriteRepository.getAllDestinationFavorite().asLiveData( + destinationFavoriteRepository.getAllDestinationFavourite().asLiveData( Dispatchers.IO, ) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/adapter/DestinationFavoriteAdapter.kt b/app/src/main/java/com/kom/skyfly/presentation/home/adapter/DestinationFavoriteAdapter.kt index f0c0c19..67997c2 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/adapter/DestinationFavoriteAdapter.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/adapter/DestinationFavoriteAdapter.kt @@ -3,42 +3,42 @@ package com.kom.skyfly.presentation.home.adapter import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup -import androidx.core.view.isVisible import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import coil.load import com.kom.skyfly.R -import com.kom.skyfly.data.model.destinationfavorite.DestinationFavorite +import com.kom.skyfly.data.model.home.destination_favourite.DestinationFavourite import com.kom.skyfly.databinding.LayoutItemTicketGridBinding +import com.kom.skyfly.utils.formatToRupiah /** Written by Komang Yuda Saputra Github : https://github.com/YudaSaputraa **/ -class DestinationFavoriteAdapter(private val itemClick: (DestinationFavorite) -> Unit) : +class DestinationFavoriteAdapter(private val itemClick: (DestinationFavourite) -> Unit) : RecyclerView.Adapter() { private val dataDiffer = AsyncListDiffer( this, - object : DiffUtil.ItemCallback() { + object : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: DestinationFavorite, - newItem: DestinationFavorite, + oldItem: DestinationFavourite, + newItem: DestinationFavourite, ): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame( - oldItem: DestinationFavorite, - newItem: DestinationFavorite, + oldItem: DestinationFavourite, + newItem: DestinationFavourite, ): Boolean { return oldItem.hashCode() == newItem.hashCode() } }, ) - fun submitData(data: List) { + fun submitData(data: List) { dataDiffer.submitList(data) } @@ -62,31 +62,18 @@ class DestinationFavoriteAdapter(private val itemClick: (DestinationFavorite) -> class ItemDestinationFavoriteViewHolder( private val binding: LayoutItemTicketGridBinding, - val itemClick: (DestinationFavorite) -> Unit, + val itemClick: (DestinationFavourite) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { @SuppressLint("SetTextI18n") - fun bindView(item: DestinationFavorite) { + fun bindView(item: DestinationFavourite) { with(item) { - binding.ivTicketImage.load(item.imageUrl) { + binding.ivTicketImage.load(item.img) { crossfade(true) error(R.mipmap.ic_launcher) } - val isLimited = item.isLimited - val discount = item.discount - if (isLimited == true) { - binding.tvLimited.isVisible = true - binding.tvLimited.text = "Limited" - } else if (discount != null) { - binding.tvLimited.isVisible = true - binding.tvLimited.text = discount - } else { - binding.tvLimited.isVisible = false - } - binding.tvCardStartFrom.text = item.origin - binding.tvCardDestination.text = "${item.origin} -> ${item.destination}" - binding.tvCardDate.text = item.dateRange + binding.tvCardDestination.text = "${item.departureCity} -> ${item.arrivalCity}" binding.tvCardAirplane.text = item.airline - binding.tvCardPrice.text = item.price + binding.tvCardPrice.text = item.price.formatToRupiah() itemView.setOnClickListener { itemClick(this) } } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/calendar/HomeCalendarFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/home/calendar/HomeCalendarFragment.kt index 1d46012..61a8291 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/calendar/HomeCalendarFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/calendar/HomeCalendarFragment.kt @@ -1,5 +1,6 @@ package com.kom.skyfly.presentation.home.calendar +import android.annotation.SuppressLint import android.graphics.Color import android.os.Bundle import android.view.LayoutInflater @@ -73,6 +74,7 @@ class HomeCalendarFragment : BottomSheetDialogFragment() { object : MonthDayBinder { override fun create(view: View) = DayViewContainer(view) + @SuppressLint("SetTextI18n") override fun bind( container: DayViewContainer, data: CalendarDay, @@ -85,32 +87,39 @@ class HomeCalendarFragment : BottomSheetDialogFragment() { DayPosition.MonthDate -> { textView.visibility = View.VISIBLE when { + startDate == data.date && (mainViewModel.roundTrip.value == false || endDate == data.date) -> { + textView.setTextColor(Color.WHITE) + textView.setBackgroundResource(R.drawable.selection_background) + binding.tvDepartureDate.text = data.date.format(dateFormatter) + mainViewModel.setStartTime(data.date.format(dateFormatter)) + if (mainViewModel.roundTrip.value == true) { + binding.tvBackDate.text = data.date.format(dateFormatter) + } else { + binding.tvBackDate.text = getString(R.string.text_dash) + } + } startDate == data.date -> { textView.setTextColor(Color.WHITE) textView.setBackgroundResource(R.drawable.selection_background) binding.tvDepartureDate.text = data.date.format(dateFormatter) mainViewModel.setStartTime(data.date.format(dateFormatter)) } - endDate == data.date -> { textView.setTextColor(Color.WHITE) textView.setBackgroundResource(R.drawable.selection_background) binding.tvBackDate.text = data.date.format(dateFormatter) mainViewModel.setReturnTime(data.date.format(dateFormatter)) } - startDate != null && endDate != null && (data.date > startDate && data.date < endDate) -> { textView.setTextColor(Color.WHITE) textView.setBackgroundResource(R.drawable.range_background) } - else -> { textView.setTextColor(Color.BLACK) textView.background = null } } } - else -> { textView.visibility = View.INVISIBLE } @@ -204,13 +213,20 @@ class HomeCalendarFragment : BottomSheetDialogFragment() { init { view.setOnClickListener { if (day.position == DayPosition.MonthDate) { - if (startDate == null || endDate != null) { - startDate = day.date - endDate = null - } else if (day.date < startDate) { + if (mainViewModel.roundTrip.value == false) { + // Single date selection logic startDate = day.date - } else { endDate = day.date + } else { + // Range selection logic + if (startDate == null || endDate != null) { + startDate = day.date + endDate = null + } else if (day.date < startDate) { + startDate = day.date + } else { + endDate = day.date + } } binding.calendarView.notifyCalendarChanged() } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeActivity.kt index a98f177..91d4383 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeActivity.kt @@ -1,57 +1,227 @@ package com.kom.skyfly.presentation.home.detail_home import android.annotation.SuppressLint +import android.content.Intent import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.util.Log -import androidx.appcompat.app.AppCompatActivity +import android.view.View +import android.widget.ScrollView +import androidx.core.view.isVisible +import coil.load +import com.kom.skyfly.R +import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket +import com.kom.skyfly.data.model.home.intent.SearchResultIntent import com.kom.skyfly.databinding.ActivityDetailHomeBinding +import com.kom.skyfly.presentation.checkout.bookersbiodata.BookersBiodataActivity +import com.kom.skyfly.presentation.common.views.ContentState +import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen +import com.xwray.groupie.GroupieAdapter import org.koin.androidx.viewmodel.ext.android.viewModel -class DetailHomeActivity : AppCompatActivity() { +class DetailHomeActivity : BaseActivity() { private val binding: ActivityDetailHomeBinding by lazy { ActivityDetailHomeBinding.inflate(layoutInflater) } + private val adapter: GroupieAdapter by lazy { + GroupieAdapter() + } + private lateinit var searchResultIntent: SearchResultIntent - private var extraId: String? = null - private var extraSeatClass: String? = null - + // Define global variables + private var returnId: String? = null + private var departureId: String? = null + private var seatClass: String? = null + private var adultCount: Int? = null + private var childCount: Int? = null + private var babyCount: Int? = null + private var roundTrip: Boolean? = null private val detailViewModel: DetailHomeViewModel by viewModel() +// private var extraRoundTrip: Boolean? = null +// private var extraId: String? = null +// private var extraSeatClass: String? = null +// private var adultCount: Int? = 0 +// private var childCount: Int? = 0 +// private var babyCount: Int? = 0 +// private var returnFlightId: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - extraId = intent.getStringExtra("EXTRA_FLIGHT_ID") - extraSeatClass = intent.getStringExtra("EXTRA_FLIGHT_SEATCLASS") + setHeaderTitle() + searchResultIntent = intent.getParcelableExtra("EXTRA_SEARCH_RESULT") ?: return + returnId = searchResultIntent.returnId + departureId = searchResultIntent.departureId + seatClass = searchResultIntent.seatClass + adultCount = searchResultIntent.adultCount + childCount = searchResultIntent.childCount + babyCount = searchResultIntent.babyCount + roundTrip = searchResultIntent.roundTrip setOnClickListener() observeData() } + private fun setHeaderTitle() { + binding.headerDetailTicketHome.tvTitleHeader.text = + getString(R.string.text_flight_detail_header) + } + private fun observeData() { - detailViewModel.getDetailTicketById(extraId!!, extraSeatClass!!).observe(this@DetailHomeActivity) { result -> - result.proceedWhen( - doOnSuccess = { - it.payload?.let { flightDetail -> - Log.d("detailTicket", "$flightDetail") - setupBinding(flightDetail) - } - }, - doOnError = { - Log.d("Error from Detail", "${it.message}") - }, - ) + if (roundTrip == true) { + getDepartureDetail() + getReturnDetail() + } else { + getDepartureDetail() } } + private fun getDepartureDetail() { + detailViewModel.getDetailTicketById(departureId!!, seatClass!!) + .observe(this@DetailHomeActivity) { result -> + result.proceedWhen( + doOnSuccess = { + Handler(Looper.getMainLooper()).postDelayed({ + val scrollView = findViewById(R.id.sv_detail_ticket) + val layoutDetailCard = + scrollView.findViewById(R.id.layout_detail_card) + binding.shimmerDetailTicket.isVisible = false + binding.svDetailTicket.isVisible = true + binding.btnSelectTicket.isEnabled = true + layoutDetailCard.isVisible = true + it.payload?.let { flightDetail -> + setupBinding(flightDetail) + binding.btnSelectTicket.setOnClickListener { + val intent = + Intent(this, BookersBiodataActivity::class.java).apply { + putExtra("EXTRAS_FLIGHT_DETAIL", flightDetail) + putExtra("EXTRA_ADULT_COUNT", adultCount) + putExtra("EXTRA_CHILD_COUNT", childCount) + putExtra("EXTRA_BABY_COUNT", babyCount) + } + startActivity(intent) + } + } + }, 1000) + Log.d("Succes from Detail", "halo") + }, + doOnError = { error -> + binding.btnSelectTicket.isEnabled = false + binding.shimmerDetailTicket.isVisible = false + if (error.exception is NoInternetException) { + binding.csvDetailTicket.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + errorHandler(error.exception) + binding.csvDetailTicket.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + errorHandler(error.exception) + binding.csvDetailTicket.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) + } else { + binding.csvDetailTicket.setState(ContentState.ERROR_GENERAL) + } + Log.e("ChooseSeatActivity", "Error: ${error.exception?.message}") + }, + doOnEmpty = { + binding.shimmerDetailTicket.isVisible = false + binding.svDetailTicket.isVisible = false + binding.btnSelectTicket.isVisible = false + }, + ) + } + } + + private fun getReturnDetail() { + detailViewModel.getDetailTicketById(returnId!!, seatClass!!) + .observe(this@DetailHomeActivity) { result -> + result.proceedWhen( + doOnSuccess = { + Handler(Looper.getMainLooper()).postDelayed({ + val scrollView = findViewById(R.id.sv_detail_ticket) + val layoutDetailCard = + scrollView.findViewById(R.id.layout_detail_return_card) + binding.shimmerDetailTicket.isVisible = false + binding.svDetailTicket.isVisible = true + binding.btnSelectTicket.isEnabled = true + layoutDetailCard.isVisible = true + + it.payload?.let { flightDetail -> + setupBindingReturnTicket(flightDetail) + binding.btnSelectTicket.setOnClickListener { + val intent = + Intent(this, BookersBiodataActivity::class.java).apply { + putExtra("EXTRAS_FLIGHT_DETAIL", flightDetail) + putExtra("EXTRA_ADULT_COUNT", adultCount) + putExtra("EXTRA_CHILD_COUNT", childCount) + putExtra("EXTRA_BABY_COUNT", babyCount) + } + startActivity(intent) + } + } + }, 1000) + Log.d("Succes from Detail", "halo") + }, + doOnError = { error -> + binding.btnSelectTicket.isEnabled = false + binding.shimmerDetailTicket.isVisible = false + if (error.exception is NoInternetException) { + binding.csvDetailTicket.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + errorHandler(error.exception) + binding.csvDetailTicket.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + errorHandler(error.exception) + binding.csvDetailTicket.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) + } else { + binding.csvDetailTicket.setState(ContentState.ERROR_GENERAL) + } + Log.e("ChooseSeatActivity", "Error: ${error.exception?.message}") + }, + doOnEmpty = { + binding.shimmerDetailTicket.isVisible = false + binding.svDetailTicket.isVisible = false + binding.btnSelectTicket.isVisible = false + }, + ) + } + } + @SuppressLint("SetTextI18n") private fun setupBinding(ticket: FlightDetailTicket) { ticket.let { - binding.layoutDetailCard.tvFlightDestinationDetailTicket.text = "${it.departureCity} - > ${it.arrivalCity} (${it.duration})" + binding.layoutDetailCard.ivAirlineDetailTicket.load(it.airplaneImg) { + crossfade(true) + error(R.mipmap.ic_launcher) + } + binding.layoutDetailCard.tvFlightDestinationDetailTicket.text = + "${it.departureCity} - > ${it.arrivalCity} (${it.duration})" binding.layoutDetailCard.tvDepartureTimeDetailTicket.text = it.departureTime binding.layoutDetailCard.tvDepartureDateDetailTicket.text = it.departureDate - binding.layoutDetailCard.tvDepartureAirportDetailTicket.text = it.departureAirport - binding.layoutDetailCard.tvTerminalDetailTicket.text = it.departureTerminal + binding.layoutDetailCard.tvDepartureAirportDetailTicket.text = + "${it.departureAirport} - ${it.departureTerminal}" binding.layoutDetailCard.tvAirplaneNameDetailTicket.text = it.airplaneName binding.layoutDetailCard.tvSeatClassDetailTicket.text = it.seatClass binding.layoutDetailCard.tvAirplaneCodeDetailTicket.text = it.code @@ -62,9 +232,58 @@ class DetailHomeActivity : AppCompatActivity() { } } + private fun setupBindingReturnTicket(ticket: FlightDetailTicket) { + ticket.let { + binding.layoutDetailReturnCard.ivAirlineDetailTicket.load(it.airplaneImg) { + crossfade(true) + error(R.mipmap.ic_launcher) + } + binding.layoutDetailReturnCard.tvFlightDestinationDetailTicket.text = + getString( + R.string.text_detail_return_flight_title, + it.departureCity, + it.arrivalCity, + it.duration, + ) + binding.layoutDetailReturnCard.tvDepartureTimeDetailTicket.text = it.departureTime + binding.layoutDetailReturnCard.tvDepartureDateDetailTicket.text = it.departureDate + binding.layoutDetailReturnCard.tvDepartureAirportDetailTicket.text = + "${it.departureAirport} - ${it.departureTerminal}" + binding.layoutDetailReturnCard.tvAirplaneNameDetailTicket.text = it.airplaneName + binding.layoutDetailReturnCard.tvSeatClassDetailTicket.text = it.seatClass + binding.layoutDetailReturnCard.tvAirplaneCodeDetailTicket.text = it.code + binding.layoutDetailReturnCard.tvFacilitiesDetailTicket.text = it.facilities + binding.layoutDetailReturnCard.tvArrivalTimeDetailTicket.text = it.arrivalTime + binding.layoutDetailReturnCard.tvArrivalDateDetailTicket.text = it.arrivalDate + binding.layoutDetailReturnCard.tvDestinationAirportDetailTicket.text = it.arrivalAirport + } + } + private fun setOnClickListener() { - binding.headerDetailTicketHome.ivBackBtnDetailTicketHome.setOnClickListener { + binding.headerDetailTicketHome.ivBack.setOnClickListener { finish() } + binding.btnSelectTicket.setOnClickListener { + detailViewModel.getDetailTicketById(returnId!!, seatClass!!) + .observe(this@DetailHomeActivity) { result -> + result.proceedWhen( + doOnSuccess = { + it.payload?.let { flightDetail -> + val intent = + Intent(this, BookersBiodataActivity::class.java).apply { + putExtra("EXTRAS_FLIGHT_DETAIL", flightDetail) + putExtra("EXTRA_ADULT_COUNT", adultCount) + putExtra("EXTRA_CHILD_COUNT", childCount) + putExtra("EXTRA_BABY_COUNT", babyCount) + } + startActivity(intent) + } + }, + doOnError = { + Log.d("Error from Detail", "${it.message}") + }, + ) + } + } } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeViewModel.kt index a72347c..0cbb88c 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/DetailHomeViewModel.kt @@ -9,7 +9,7 @@ class DetailHomeViewModel( private val repo: FlightTicketRepository, ) : ViewModel() { fun getDetailTicketById( - id: String, - seatClass: String, - ) = repo.getDetailTicket(id = id, seatClass = seatClass).asLiveData(Dispatchers.IO) + id: String?, + seatClass: String?, + ) = repo.getDetailTicket(id = id.orEmpty(), seatClass = seatClass).asLiveData(Dispatchers.IO) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/view_item/Items.kt b/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/view_item/Items.kt new file mode 100644 index 0000000..4b9047d --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/presentation/home/detail_home/view_item/Items.kt @@ -0,0 +1,40 @@ +package com.kom.skyfly.presentation.home.detail_home.view_item + +import android.annotation.SuppressLint +import android.view.View +import com.kom.skyfly.R +import com.kom.skyfly.data.model.home.flight.FlightTicket +import com.kom.skyfly.databinding.LayoutItemCardTicketDetailHomeBinding +import com.xwray.groupie.viewbinding.BindableItem + +class Items( + private val data: FlightTicket, + private val onItemClick: (item: FlightTicket) -> Unit, +) : BindableItem() { + @SuppressLint("SetTextI18n") + override fun bind( + viewBinding: LayoutItemCardTicketDetailHomeBinding, + position: Int, + ) { + with(viewBinding) { + tvFlightDestinationDetailTicket.text = + "${data.departureCity} - > ${data.arrivalCity} (${data.duration})" + tvDepartureTimeDetailTicket.text = data.departureTime + tvDepartureDateDetailTicket.text = data.departureDate + tvDepartureAirportDetailTicket.text = "${data.departureAirport} - ${data.departureTerminal}" + tvAirplaneNameDetailTicket.text = data.airplaneName + tvSeatClassDetailTicket.text = data.seatClass + tvAirplaneCodeDetailTicket.text = data.code + tvFacilitiesDetailTicket.text = data.facilities + tvArrivalTimeDetailTicket.text = data.arrivalTime + tvArrivalDateDetailTicket.text = data.arrivalDate + tvDestinationAirportDetailTicket.text = data.arrivalAirport + } + } + + override fun getLayout(): Int = R.layout.layout_item_card_ticket_detail_home + + override fun initializeViewBinding(view: View): LayoutItemCardTicketDetailHomeBinding { + return LayoutItemCardTicketDetailHomeBinding.bind(view) + } +} diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchFragment.kt index 196bdd3..ccd1ef8 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchFragment.kt @@ -4,15 +4,21 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast +import androidx.core.view.isVisible import androidx.lifecycle.map import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.kom.skyfly.R +import com.kom.skyfly.data.model.home.search_history.SearchDestinationHistory import com.kom.skyfly.databinding.FragmentSearchBinding +import com.kom.skyfly.presentation.home.search.adapter.SearchDestinationHistoryAdapter +import com.kom.skyfly.presentation.home.search.adapter.SearchDestinationHistoryListener import com.kom.skyfly.presentation.home.search.viewitems.Items import com.kom.skyfly.presentation.main.MainViewModel +import com.kom.skyfly.utils.afterTextChanged import com.kom.skyfly.utils.proceedWhen import com.xwray.groupie.GroupieAdapter import org.koin.androidx.viewmodel.ext.android.activityViewModel @@ -25,6 +31,19 @@ class SearchFragment : BottomSheetDialogFragment() { private val adapter: GroupieAdapter by lazy { GroupieAdapter() } + private val searchHistoryAdapter: SearchDestinationHistoryAdapter by lazy { + SearchDestinationHistoryAdapter( + itemClick = { searchDestinationHistory -> + binding.layoutHomeSearchBar.etHomeSearch.setText(searchDestinationHistory.searchDestinationHistory) + }, + searchDestinationHistoryListener = + object : SearchDestinationHistoryListener { + override fun onDeleteItemClicked(searchDestinationHistory: SearchDestinationHistory) { + searchViewModel.deleteSearchHistory(searchDestinationHistory) + } + }, + ) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -45,18 +64,18 @@ class SearchFragment : BottomSheetDialogFragment() { ) { super.onViewCreated(view, savedInstanceState) setOnClickListener() + setList() val maxPeekHeight = resources.getDimensionPixelSize( R.dimen.max_bottom_sheet_height, ) setupBinding() - getAirportData() + getRecentSearchAirport() setBottomSheetMaxHeight(maxPeekHeight) - // Extract names from the mockList } - private fun getAirportData() { - searchViewModel.getAllAirports().observe(viewLifecycleOwner) { response -> + private fun getAirportData(city: String?) { + searchViewModel.getAllAirports(city = city).observe(viewLifecycleOwner) { response -> response.proceedWhen( doOnSuccess = { result -> result.payload?.let { sectionedSearch -> @@ -66,6 +85,7 @@ class SearchFragment : BottomSheetDialogFragment() { sectionedSearch.map { data -> Items(data) { item -> mainViewModel.setDestination(item) + observeData(item.city) dismiss() } } @@ -77,6 +97,36 @@ class SearchFragment : BottomSheetDialogFragment() { } } + private fun observeData(searchHistory: String) { + searchViewModel.insertSearchHistory(searchHistory) + .observe(viewLifecycleOwner) { result -> + result.proceedWhen( + doOnSuccess = { + Toast.makeText(requireContext(), "success", Toast.LENGTH_SHORT).show() + }, + doOnError = { + Toast.makeText(requireContext(), "error", Toast.LENGTH_SHORT).show() + }, + ) + } + } + + private fun getRecentSearchAirport() { + searchViewModel.getAllSearchHistory().observe(viewLifecycleOwner) { result -> + result.proceedWhen( + doOnSuccess = { + binding.tvHomeDeleteRecentSearches.isVisible = true + binding.tvTitleHomeRecentSearches.isVisible = true + binding.rvRecentDestinationSearch.isVisible = true + binding.rvHomeSearchPage.isVisible = false + result.payload?.let { + searchHistoryAdapter.submitData(it) + } + }, + ) + } + } + private fun setupBinding() { binding.rvHomeSearchPage.apply { layoutManager = LinearLayoutManager(requireContext()) @@ -84,10 +134,16 @@ class SearchFragment : BottomSheetDialogFragment() { } } + private fun setList() { + binding.rvRecentDestinationSearch.layoutManager = LinearLayoutManager(requireContext()) + binding.rvRecentDestinationSearch.adapter = searchHistoryAdapter + } + private fun setBottomSheetMaxHeight(maxPeekHeight: Int) { dialog?.setOnShowListener { val bottomSheetDialog = it as? BottomSheetDialog - val bottomSheet = bottomSheetDialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + val bottomSheet = + bottomSheetDialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet) bottomSheet?.let { sheet -> val behavior = BottomSheetBehavior.from(sheet) behavior.peekHeight = maxPeekHeight @@ -100,5 +156,18 @@ class SearchFragment : BottomSheetDialogFragment() { binding.layoutHomeSearchBar.ivCloseSearch.setOnClickListener { dismiss() } + binding.layoutHomeSearchBar.etHomeSearch.afterTextChanged { + if (it.isNotEmpty()) { + binding.rvHomeSearchPage.isVisible = true + binding.rvRecentDestinationSearch.isVisible = false + binding.tvHomeDeleteRecentSearches.isVisible = false + getAirportData(it) + } else { + binding.tvTitleHomeRecentSearches.isVisible = true + binding.rvHomeSearchPage.isVisible = false + binding.rvRecentDestinationSearch.isVisible = true + getRecentSearchAirport() + } + } } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchViewModel.kt index 8ff6c4b..c361e3a 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search/SearchViewModel.kt @@ -1,12 +1,37 @@ package com.kom.skyfly.presentation.home.search +import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.kom.skyfly.data.model.home.search_history.SearchDestinationHistory import com.kom.skyfly.data.repository.home.airport.AirportRepository +import com.kom.skyfly.utils.ResultWrapper import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch class SearchViewModel( private val repo: AirportRepository, ) : ViewModel() { - fun getAllAirports() = repo.getAllAirportData().asLiveData(Dispatchers.IO) + fun getAllAirports(city: String? = null) = repo.getAllAirportData(city = city).asLiveData(Dispatchers.IO) + + fun getAllSearchHistory() = repo.getUserSearchDestinationHistory().asLiveData(Dispatchers.IO) + + fun deleteSearchHistory(item: SearchDestinationHistory) { + viewModelScope.launch(Dispatchers.IO) { + repo.deleteSearchDestinationHistory(item).collect() + } + } + + fun deleteAllSearchHistory() { + viewModelScope.launch { + repo.deleteAllSearchDestinationHistory().collect() + } + } + + fun insertSearchHistory(searchDestinationHistory: String): LiveData> { + return repo.createSearchDestinationHistory(searchDestinationHistory) + .asLiveData(Dispatchers.IO) + } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search/adapter/SearchDestinationHistoryAdapter.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search/adapter/SearchDestinationHistoryAdapter.kt new file mode 100644 index 0000000..92975eb --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search/adapter/SearchDestinationHistoryAdapter.kt @@ -0,0 +1,80 @@ +package com.kom.skyfly.presentation.home.search.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.kom.skyfly.data.model.home.search_history.SearchDestinationHistory +import com.kom.skyfly.databinding.LayoutItemAirportRecentSearchBinding + +class SearchDestinationHistoryAdapter( + private val itemClick: ( + SearchDestinationHistory, + ) -> Unit, + private val searchDestinationHistoryListener: SearchDestinationHistoryListener? = null, +) : RecyclerView.Adapter() { + class SearchDestinationHistoryViewHolder( + private val binding: LayoutItemAirportRecentSearchBinding, + val itemClick: (SearchDestinationHistory) -> Unit, + private val searchDestinationHistoryListener: SearchDestinationHistoryListener?, + ) : RecyclerView.ViewHolder(binding.root) { + fun bindView(item: SearchDestinationHistory) { + binding.tvFlightHomeRecentSearch.text = item.searchDestinationHistory + itemView.setOnClickListener { itemClick(item) } + setClickListeners(item) + } + + private fun setClickListeners(item: SearchDestinationHistory) { + with(binding) { + ivDeleteItem.setOnClickListener { searchDestinationHistoryListener?.onDeleteItemClicked(item) } + } + } + } + + private val dataDiffer = + AsyncListDiffer( + this, + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SearchDestinationHistory, + newItem: SearchDestinationHistory, + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: SearchDestinationHistory, + newItem: SearchDestinationHistory, + ): Boolean { + return oldItem.hashCode() == newItem.hashCode() + } + }, + ) + + fun submitData(data: List) { + dataDiffer.submitList(data) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): SearchDestinationHistoryViewHolder { + val binding = + LayoutItemAirportRecentSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SearchDestinationHistoryViewHolder(binding, itemClick, searchDestinationHistoryListener) + } + + override fun onBindViewHolder( + holder: SearchDestinationHistoryViewHolder, + position: Int, + ) { + (holder.bindView(dataDiffer.currentList[position])) + } + + override fun getItemCount(): Int = dataDiffer.currentList.size +} + +interface SearchDestinationHistoryListener { + fun onDeleteItemClicked(searchDestinationHistory: SearchDestinationHistory) +} diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search/viewitems/Items.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search/viewitems/Items.kt index 5029fe3..6d93b9a 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search/viewitems/Items.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search/viewitems/Items.kt @@ -24,7 +24,6 @@ class Items( } } - override fun getLayout(): Int = R.layout.layout_item_airport_search override fun initializeViewBinding(view: View): LayoutItemAirportSearchBinding { diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultActivity.kt index 017c83a..6182b38 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultActivity.kt @@ -2,15 +2,23 @@ package com.kom.skyfly.presentation.home.search_result import android.content.Intent import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.util.Log -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager +import com.kom.skyfly.R +import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.data.model.home.Calendar +import com.kom.skyfly.data.model.home.intent.SearchResultIntent import com.kom.skyfly.databinding.ActivitySearchResultBinding +import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.presentation.home.detail_home.DetailHomeActivity import com.kom.skyfly.presentation.home.search_result.view_items.CalendarAdapter import com.kom.skyfly.presentation.home.search_result.view_items.Items +import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.Section @@ -20,7 +28,7 @@ import java.time.format.DateTimeFormatter import java.time.format.TextStyle import java.util.Locale -class SearchResultActivity : AppCompatActivity() { +class SearchResultActivity : BaseActivity() { private val binding: ActivitySearchResultBinding by lazy { ActivitySearchResultBinding.inflate(layoutInflater) } @@ -29,8 +37,8 @@ class SearchResultActivity : AppCompatActivity() { } private val calendarAdapter: CalendarAdapter by lazy { CalendarAdapter { + calendarAdapter.setSelectedDate(it.date) getAllTicketData(it.date) - Toast.makeText(this, "${it.date}", Toast.LENGTH_SHORT).show() } } @@ -38,14 +46,28 @@ class SearchResultActivity : AppCompatActivity() { private var departureAirport: String? = null private var arrivalAirport: String? = null private var departureTime: String? = null + private var adultCount: Int? = null + private var childrenCount: Int? = null + private var babyCount: Int? = null + private var totalPassenger: Int? = null + private var seatClass: String? = null + private var extraRoundTrip: Boolean? = null + private var returnDate: String? = null + private var flightId: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - + seatClass = intent.getStringExtra("EXTRA_SEAT_CLASS") departureAirport = intent.getStringExtra("EXTRA_DEPARTURE_AIRPORT") arrivalAirport = intent.getStringExtra("EXTRA_ARRIVAL_AIRPORT") departureTime = intent.getStringExtra("EXTRA_DEPARTURE_TIME") + returnDate = intent.getStringExtra("EXTRA_RETURN_TIME") + adultCount = intent.getIntExtra("EXTRA_ADULT_COUNT", 0) + totalPassenger = intent.getIntExtra("EXTRA_TOTAL_PASSENGER", 0) + childrenCount = intent.getIntExtra("EXTRA_CHILD_COUNT", 0) + babyCount = intent.getIntExtra("EXTRA_BABY_COUNT", 0) + extraRoundTrip = intent.getBooleanExtra("EXTRA_ROUND_TRIP", false) setOnClickListener() setupBinding() getCalendarData() @@ -54,7 +76,8 @@ class SearchResultActivity : AppCompatActivity() { private fun getCalendarData() { val recyclerView = binding.rvDateSearchResult - recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) + recyclerView.layoutManager = + LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) val dateTime = YearMonth.parse(departureTime, DateTimeFormatter.ofPattern("yyyy-MM-dd")) @@ -71,13 +94,17 @@ class SearchResultActivity : AppCompatActivity() { calendarAdapter.submitData(days) } - private fun getAllTicketData(date: String) { - searchResultViewModel.getAllFlightTicket( + private fun getAllReturnTicketData(date: String) { + searchResultViewModel.getReturnFlightTicket( page = 1, arrivalAirport = arrivalAirport!!, departureAirport = departureAirport!!, departureDate = date, - seatClass = "ECONOMY", + seatClass = seatClass, + adult = adultCount, + children = childrenCount, + baby = babyCount, + returnDate = returnDate, ).observe(this) { response -> response.proceedWhen( doOnSuccess = { @@ -89,16 +116,23 @@ class SearchResultActivity : AppCompatActivity() { val data = sectionedSearch.map { data -> Items(data) { item -> + Log.d("dari return ticket data", "$item") + val searchResultIntent = + SearchResultIntent( + returnId = item?.id, + departureId = flightId, + seatClass = item?.seatClass, + adultCount = adultCount, + childCount = childrenCount, + babyCount = babyCount, + roundTrip = extraRoundTrip, + ) val intent = Intent( this@SearchResultActivity, DetailHomeActivity::class.java, ).apply { - putExtra("EXTRA_FLIGHT_ID", item?.id) - putExtra( - "EXTRA_FLIGHT_SEATCLASS", - item?.seatClass, - ) + putExtra("EXTRA_SEARCH_RESULT", searchResultIntent) } startActivity(intent) } @@ -114,7 +148,7 @@ class SearchResultActivity : AppCompatActivity() { adapter.clear() }, doOnError = { - Log.e("SearchResultActivity", "Error fetching data: ${it.exception?.message}") + Log.e("SearchResultActivityReturn", "Error fetching data: ${it.exception?.message}") }, doOnLoading = { Log.d("SearchResultActivity", "Loading data") @@ -123,7 +157,117 @@ class SearchResultActivity : AppCompatActivity() { } } + private fun getAllTicketData(date: String) { + searchResultViewModel.getAllFlightTicket( + page = 1, + arrivalAirport = arrivalAirport!!, + departureAirport = departureAirport!!, + departureDate = date, + seatClass = seatClass, + adult = adultCount, + children = childrenCount, + baby = babyCount, + returnDate = returnDate, + ).observe(this) { response -> + response.proceedWhen( + doOnSuccess = { + Handler(Looper.getMainLooper()).postDelayed({ + binding.shimmerTicket.isVisible = false + binding.rvDateSearchResult.isVisible = true + binding.rvItemTicketsSearchResult.isVisible = true + it.payload?.let { sectionedSearch -> + val sections = + sectionedSearch.map { + Section().apply { + val data = + sectionedSearch.map { data -> + Items(data) { item -> + if (extraRoundTrip == true) { + flightId = item?.id + getAllReturnTicketData(departureTime!!) + } else { + val searchResultIntent = + SearchResultIntent( + departureId = item?.id, + seatClass = item?.seatClass, + adultCount = adultCount, + childCount = childrenCount, + babyCount = babyCount, + roundTrip = extraRoundTrip, + returnId = "", + ) + val intent = + Intent( + this@SearchResultActivity, + DetailHomeActivity::class.java, + ).apply { + putExtra("EXTRA_SEARCH_RESULT", searchResultIntent) + } + startActivity(intent) + } + } + } + addAll(data) + } + } + adapter.clear() + adapter.update(sections) + } + }, 1000) + Log.d("SearchResultActivity", "Data fetched successfully") + }, + doOnEmpty = { + adapter.clear() + binding.shimmerTicket.isVisible = false + binding.csvTicketResult.setState( + ContentState.EMPTY, + getString(R.string.text_no_tickets_for_selected_date), + ) + }, + doOnError = { error -> + binding.shimmerTicket.isVisible = false + if (error.exception is NoInternetException) { + binding.csvTicketResult.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + errorHandler(error.exception) + binding.csvTicketResult.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + errorHandler(error.exception) + binding.csvTicketResult.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) + } else { + binding.csvTicketResult.setState(ContentState.ERROR_GENERAL) + } + Log.e("SearchResultActivity", "Error: ${error.exception?.message}") + }, + doOnLoading = { + binding.shimmerTicket.isVisible = true + binding.csvTicketResult.isVisible = false + binding.rvItemTicketsSearchResult.isVisible = false + binding.rvDateSearchResult.isVisible = true + }, + ) + } + } + private fun setupBinding() { + binding.headerFlightSearchResult.tvTitleHeader.text = + getString( + R.string.text_header_search_result, + departureAirport, + arrivalAirport, + totalPassenger.toString(), + seatClass, + ) binding.rvItemTicketsSearchResult.apply { layoutManager = LinearLayoutManager(this@SearchResultActivity) adapter = this@SearchResultActivity.adapter @@ -131,7 +275,7 @@ class SearchResultActivity : AppCompatActivity() { } private fun setOnClickListener() { - binding.headerFlightSearchResult.ivBackBtnSearchResult.setOnClickListener { + binding.headerFlightSearchResult.ivBack.setOnClickListener { finish() } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultViewModel.kt index ea01519..7b0a46a 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/SearchResultViewModel.kt @@ -15,7 +15,13 @@ class SearchResultViewModel( departureAirport: String, arrivalAirport: String, departureDate: String, - seatClass: String, + returnDate: String? = null, + seatClass: String?, + limit: Int? = 20, + arrivalDate: String? = null, + adult: Int? = 1, + children: Int? = 0, + baby: Int? = 0, ) = flightRepository.getAllTicket( search = search, page = page, @@ -23,5 +29,39 @@ class SearchResultViewModel( arrivalAirport = arrivalAirport, departureDate = departureDate, seatClass = seatClass, + limit = limit, + arrivalDate = arrivalDate, + adult = adult, + children = children, + baby = baby, + returnDate = returnDate, + ).asLiveData(Dispatchers.IO) + + fun getReturnFlightTicket( + search: String? = null, + page: Int, + departureAirport: String, + arrivalAirport: String, + departureDate: String, + returnDate: String? = null, + seatClass: String?, + limit: Int? = 20, + arrivalDate: String? = null, + adult: Int? = 1, + children: Int? = 0, + baby: Int? = 0, + ) = flightRepository.getReturnTicket( + search = search, + page = page, + departureAirport = departureAirport, + arrivalAirport = arrivalAirport, + departureDate = departureDate, + seatClass = seatClass, + limit = limit, + arrivalDate = arrivalDate, + adult = adult, + children = children, + baby = baby, + returnDate = returnDate, ).asLiveData(Dispatchers.IO) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/CalendarAdapter.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/CalendarAdapter.kt index 3ab09e9..f2f8d2a 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/CalendarAdapter.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/CalendarAdapter.kt @@ -1,25 +1,48 @@ package com.kom.skyfly.presentation.home.search_result.view_items import android.annotation.SuppressLint +import android.graphics.Color import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import com.kom.skyfly.R import com.kom.skyfly.data.model.home.Calendar import com.kom.skyfly.databinding.LayoutDateBinding class CalendarAdapter(private val itemClick: (Calendar) -> Unit) : RecyclerView.Adapter() { + private var selectedDate: String? = null + class ItemCalendarViewHolder( private val binding: LayoutDateBinding, val itemClick: (Calendar) -> Unit, ) : RecyclerView.ViewHolder(binding.root) { @SuppressLint("ResourceAsColor") - fun bindView(item: Calendar) { + fun bindView( + item: Calendar, + isSelected: Boolean, + ) { with(item) { binding.tvCalendarDay.text = day binding.tvCalendarDate.text = date + val context = binding.root.context + if (isSelected) { + binding.cvCalendar.setCardBackgroundColor( + ContextCompat.getColor( + context, + R.color.md_theme_primaryFixed_mediumContrast, + ), + ) + binding.tvCalendarDay.setTextColor(Color.WHITE) + binding.tvCalendarDate.setTextColor(Color.WHITE) + } else { + binding.cvCalendar.setCardBackgroundColor(Color.WHITE) + binding.tvCalendarDay.setTextColor(Color.BLACK) + binding.tvCalendarDate.setTextColor(Color.BLACK) + } itemView.setOnClickListener { itemClick(this) @@ -66,6 +89,13 @@ class CalendarAdapter(private val itemClick: (Calendar) -> Unit) : holder: ItemCalendarViewHolder, position: Int, ) { - holder.bindView(asyncDataDiffer.currentList[position]) + val item = asyncDataDiffer.currentList[position] + val isSelected = item.date == selectedDate + holder.bindView(item, isSelected) + } + + fun setSelectedDate(date: String) { + selectedDate = date + notifyDataSetChanged() } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/Items.kt b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/Items.kt index 90ca510..7b7181f 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/Items.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/search_result/view_items/Items.kt @@ -2,9 +2,12 @@ package com.kom.skyfly.presentation.home.search_result.view_items import android.annotation.SuppressLint import android.view.View +import coil.load import com.kom.skyfly.R import com.kom.skyfly.data.model.home.flight.FlightTicket +import com.kom.skyfly.data.model.home.flight_detail.FlightDetailTicket import com.kom.skyfly.databinding.LayoutItemCardSearchResultBinding +import com.kom.skyfly.utils.formatToRupiah import com.xwray.groupie.viewbinding.BindableItem class Items( @@ -17,13 +20,60 @@ class Items( position: Int, ) { with(viewBinding) { + ivIcAirlineSearchResult.load(data?.airplaneImg) { + crossfade(true) + error(R.drawable.img_airline) + } + tvTimeDepartureSearchResult.text = data?.departureTime + tvTimeArrivalSearchResult.text = data?.arrivalTime + tvTargetDepartureSearchResult.text = data?.departureCountryCode + tvTargetArrivalSearchResult.text = data?.arrivalCountryCode + tvFlightDurationSearchResult.text = data?.duration + if (data?.directNotes == false) { + tvTransitOptionSearchResult.text = "Direct" + } else { + tvTransitOptionSearchResult.text = "Transit" + } + tvPriceSearchResult.text = data?.price.formatToRupiah().toString() + tvAirlineSearchResult.text = "${data?.airplaneName} - ${data?.seatClass}" + viewBinding.root.setOnClickListener { + onItemClick(data) + } + } + } + + override fun getLayout(): Int = R.layout.layout_item_card_search_result + + override fun initializeViewBinding(view: View): LayoutItemCardSearchResultBinding { + return LayoutItemCardSearchResultBinding.bind(view) + } +} + +class FlightDetailItem( + private val data: FlightDetailTicket?, + private val onItemClick: (item: FlightDetailTicket?) -> Unit, +) : BindableItem() { + @SuppressLint("SetTextI18n") + override fun bind( + viewBinding: LayoutItemCardSearchResultBinding, + position: Int, + ) { + with(viewBinding) { + ivIcAirlineSearchResult.load(data?.airplaneImg) { + crossfade(true) + error(R.mipmap.ic_launcher) + } tvTimeDepartureSearchResult.text = data?.departureTime tvTimeArrivalSearchResult.text = data?.arrivalTime tvTargetDepartureSearchResult.text = data?.departureCountryCode tvTargetArrivalSearchResult.text = data?.arrivalCountryCode tvFlightDurationSearchResult.text = data?.duration - tvTransitOptionSearchResult.text = "Direct" - tvPriceSearchResult.text = data?.price.toString() + if (data?.transitNotes == false) { + tvTransitOptionSearchResult.text = "Direct" + } else { + tvTransitOptionSearchResult.text = "Transit" + } + tvPriceSearchResult.text = data?.price.formatToRupiah().toString() tvAirlineSearchResult.text = "${data?.airplaneName} - ${data?.seatClass}" viewBinding.root.setOnClickListener { onItemClick(data) diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassFragment.kt index f140791..a9446fa 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassFragment.kt @@ -4,12 +4,27 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.kom.skyfly.R +import com.kom.skyfly.data.model.home.seat_class.SeatClassHome import com.kom.skyfly.databinding.FragmentSeatClassBinding +import com.kom.skyfly.presentation.home.seatclass.adapter.SeatClassAdapter +import com.kom.skyfly.presentation.main.MainViewModel +import org.koin.androidx.viewmodel.ext.android.activityViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel class SeatClassFragment : BottomSheetDialogFragment() { private lateinit var binding: FragmentSeatClassBinding + private val seatClassViewModel: SeatClassViewModel by viewModel() + private val mainViewModel: MainViewModel by activityViewModel() + private var seatClassName: String? = null + private val adapter: SeatClassAdapter by lazy { + SeatClassAdapter { item -> + item.let { + seatClassName = it.seatClassName + } + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -19,8 +34,36 @@ class SeatClassFragment : BottomSheetDialogFragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_seat_class, container, false) + ): View { + binding = FragmentSeatClassBinding.inflate(layoutInflater, container, false) + return binding.root + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + setupBinding(seatClassViewModel.getSeatClass()) + setOnClickListener() + } + + private fun setupBinding(data: List) { + binding.rvSeatclassCategories.apply { + layoutManager = + LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + adapter = this@SeatClassFragment.adapter + } + adapter.submitData(data) + } + + private fun setOnClickListener() { + binding.ivHomeCloseSeatclass.setOnClickListener { + dismiss() + } + binding.btnSeatclassSave.setOnClickListener { + mainViewModel.setSeatClass(seatClassName) + dismiss() + } } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassViewModel.kt new file mode 100644 index 0000000..816f5f7 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/SeatClassViewModel.kt @@ -0,0 +1,8 @@ +package com.kom.skyfly.presentation.home.seatclass + +import androidx.lifecycle.ViewModel +import com.kom.skyfly.data.repository.home.flight_ticket.FlightTicketRepository + +class SeatClassViewModel(private val repo: FlightTicketRepository) : ViewModel() { + fun getSeatClass() = repo.getSeatClassTicket() +} diff --git a/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/adapter/SeatClassAdapter.kt b/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/adapter/SeatClassAdapter.kt new file mode 100644 index 0000000..875537e --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/presentation/home/seatclass/adapter/SeatClassAdapter.kt @@ -0,0 +1,114 @@ +package com.kom.skyfly.presentation.home.seatclass.adapter + +import android.graphics.Color +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.recyclerview.widget.AsyncListDiffer +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.kom.skyfly.R +import com.kom.skyfly.data.model.home.seat_class.SeatClassHome +import com.kom.skyfly.databinding.LayoutItemSeatclassCardBinding + +class SeatClassAdapter(private val itemClick: (SeatClassHome) -> Unit) : + RecyclerView.Adapter() { + private var selectedItemPosition = RecyclerView.NO_POSITION + + private val dataDiffer = + AsyncListDiffer( + this, + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: SeatClassHome, + newItem: SeatClassHome, + ): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame( + oldItem: SeatClassHome, + newItem: SeatClassHome, + ): Boolean { + return oldItem == newItem + } + }, + ) + + fun submitData(data: List) { + dataDiffer.submitList(data) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): ItemSeatClassViewHolder { + val binding = + LayoutItemSeatclassCardBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ) + return ItemSeatClassViewHolder(binding, itemClick) + } + + override fun onBindViewHolder( + holder: ItemSeatClassViewHolder, + position: Int, + ) { + holder.bindView(dataDiffer.currentList[position], position == selectedItemPosition) + } + + override fun getItemCount(): Int = dataDiffer.currentList.size + + inner class ItemSeatClassViewHolder( + private val binding: LayoutItemSeatclassCardBinding, + private val itemClick: (SeatClassHome) -> Unit, + ) : RecyclerView.ViewHolder(binding.root) { + fun bindView( + item: SeatClassHome, + isSelected: Boolean, + ) { + with(item) { + binding.tvSeatclassTitle.text = seatClassName + val bgColor = + if (isSelected) { + ContextCompat.getColor( + binding.root.context, + R.color.md_theme_primaryFixed_mediumContrast, + ) + } else { + ContextCompat.getColor(binding.root.context, android.R.color.white) + } + binding.cvSeatClass.setCardBackgroundColor(bgColor) + + val titleTextColor = + if (isSelected) { + Color.WHITE + } else { + ContextCompat.getColor(binding.root.context, android.R.color.black) + } + if (isSelected) { + binding.ivChecklist.isVisible = true + } else { + binding.ivChecklist.isVisible = false + } + binding.tvSeatclassTitle.setTextColor(titleTextColor) + itemView.setOnClickListener { + itemClick(item) + setSelected(adapterPosition) + } + } + } + + private fun setSelected(position: Int) { + if (selectedItemPosition != position) { + val previousSelected = selectedItemPosition + selectedItemPosition = position + notifyItemChanged(previousSelected) + notifyItemChanged(selectedItemPosition) + } + } + } +} diff --git a/app/src/main/java/com/kom/skyfly/presentation/login/LoginActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/login/LoginActivity.kt index ef97db1..1d41b7d 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/login/LoginActivity.kt @@ -73,7 +73,7 @@ class LoginActivity : AppCompatActivity() { result.proceedWhen( doOnSuccess = { binding.pbLoading.isVisible = false - binding.btnLogin.isVisible = true + binding.btnLogin.isEnabled = true it.payload?.let { payload -> val token = payload.token loginViewModel.saveUserToken(token) @@ -84,7 +84,10 @@ class LoginActivity : AppCompatActivity() { doOnError = { binding.pbLoading.isVisible = false if (it.exception is NoInternetException) { - binding.csvLogin.setState(ContentState.ERROR_NETWORK_GENERAL, "Tidak ada internet!") + binding.csvLogin.setState( + ContentState.ERROR_NETWORK_GENERAL, + "Tidak ada internet!", + ) } else { Toasty.error( this@LoginActivity, @@ -93,11 +96,11 @@ class LoginActivity : AppCompatActivity() { true, ).show() } - binding.btnLogin.isVisible = true + binding.btnLogin.isEnabled = true }, doOnLoading = { binding.pbLoading.isVisible = true - binding.btnLogin.isVisible = false + binding.btnLogin.isEnabled = false }, ) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/main/MainActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/main/MainActivity.kt index 06a0410..a10ffbd 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/main/MainActivity.kt @@ -8,7 +8,7 @@ import androidx.navigation.ui.setupWithNavController import com.kom.skyfly.R import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.databinding.ActivityMainBinding -import com.kom.skyfly.presentation.bottomsheetsdialog.BottomSheetsDialogFragment +import com.kom.skyfly.presentation.bottomsheetsdialog.NotLoginBottomSheets import com.kom.skyfly.presentation.checkout.bookersbiodata.BookersBiodataActivity import org.koin.androidx.viewmodel.ext.android.viewModel @@ -32,7 +32,7 @@ class MainActivity : BaseActivity() { super.onCreate(savedInstanceState) setContentView(binding.root) setBottomNavbar() - navigate() +// navigate() } private fun setBottomNavbar() { @@ -52,7 +52,7 @@ class MainActivity : BaseActivity() { private fun openNotLoggedInModal() { if (!supportFragmentManager.isStateSaved) { - val bottomSheetFragment = BottomSheetsDialogFragment() + val bottomSheetFragment = NotLoginBottomSheets() bottomSheetFragment.show(supportFragmentManager, bottomSheetFragment.tag) } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/main/MainViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/main/MainViewModel.kt index fe670e5..3e9c9ce 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/main/MainViewModel.kt @@ -101,4 +101,20 @@ class MainViewModel( passengerBabyCountLiveData.postValue(count) } } + + // Set seatClassData + private val _seatClass = MutableLiveData() + val seatClass: LiveData get() = _seatClass + + fun setSeatClass(value: String?) { + _seatClass.value = value + } + + // Set roundTrip + private val _roundTrip = MutableLiveData(false) + val roundTrip: LiveData get() = _roundTrip + + fun setRoundTrip(value: Boolean?) { + _roundTrip.value = value + } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/notification/NotificationFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/notification/NotificationFragment.kt index 3fe2287..b7f1cdf 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/notification/NotificationFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/notification/NotificationFragment.kt @@ -2,17 +2,24 @@ package com.kom.skyfly.presentation.notification import android.content.Intent import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import com.kom.skyfly.R +import com.kom.skyfly.core.BaseActivity import com.kom.skyfly.data.model.notification.Notification import com.kom.skyfly.databinding.FragmentNotificationBinding import com.kom.skyfly.presentation.common.views.ContentState import com.kom.skyfly.presentation.notification.adapter.NotificationAdapter import com.kom.skyfly.presentation.notificationdetail.NotificationDetailActivity import com.kom.skyfly.utils.NoInternetException +import com.kom.skyfly.utils.ServerErrorException +import com.kom.skyfly.utils.UnAuthorizeException import com.kom.skyfly.utils.proceedWhen import org.koin.androidx.viewmodel.ext.android.viewModel @@ -57,11 +64,13 @@ class NotificationFragment : Fragment() { notificationViewModel.getAllNotification().observe(viewLifecycleOwner) { result -> result.proceedWhen( doOnSuccess = { - binding.srlNotification.isRefreshing = false - binding.shmProgressNotification.isVisible = false - binding.csvNotification.setState(ContentState.SUCCESS) - binding.rvNotification.isVisible = true - it.payload?.let { data -> bindNotificationList(data) } + Handler(Looper.getMainLooper()).postDelayed({ + binding.srlNotification.isRefreshing = false + binding.shmProgressNotification.isVisible = false + binding.csvNotification.setState(ContentState.SUCCESS) + binding.rvNotification.isVisible = true + it.payload?.let { data -> bindNotificationList(data) } + }, 1000) }, doOnLoading = { binding.srlNotification.isRefreshing = false @@ -73,14 +82,32 @@ class NotificationFragment : Fragment() { binding.csvNotification.setState(ContentState.EMPTY) binding.shmProgressNotification.isVisible = false }, - doOnError = { + doOnError = { error -> binding.srlNotification.isRefreshing = false - if (it.exception is NoInternetException) { - binding.csvNotification.setState(ContentState.ERROR_NETWORK) + binding.shmProgressNotification.isVisible = false + + if (error.exception is NoInternetException) { + binding.csvNotification.setState( + ContentState.ERROR_NETWORK, + getString(R.string.no_internet_connection), + ) + } else if (error.exception is UnAuthorizeException) { + (activity as BaseActivity).errorHandler(error.exception) + binding.csvNotification.setState( + ContentState.ERROR_NETWORK_GENERAL, + getString(R.string.text_session_expired_please_login_again), + ) + } else if (error.exception is ServerErrorException) { + (activity as BaseActivity).errorHandler(error.exception) + binding.csvNotification.setState( + ContentState.ERROR_NETWORK, + getString(R.string.text_server_error_please_try_again_later), + R.drawable.img_empty_data, + ) } else { binding.csvNotification.setState(ContentState.ERROR_GENERAL) } - binding.shmProgressNotification.isVisible = false + Log.e("NotificationFragment", "Error: ${error.exception?.message}") }, ) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/notificationdetail/NotificationDetailActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/notificationdetail/NotificationDetailActivity.kt index 4764710..f5d3849 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/notificationdetail/NotificationDetailActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/notificationdetail/NotificationDetailActivity.kt @@ -34,9 +34,6 @@ class NotificationDetailActivity : AppCompatActivity() { binding.ivBack.setOnClickListener { onBackPressed() } - binding.ivTrash.setOnClickListener { - notification?.let { deleteNotification(it) } - } } private fun displayNotificationDetails(notification: Notification) { @@ -49,6 +46,9 @@ class NotificationDetailActivity : AppCompatActivity() { "Promotion" -> { binding.ivNotificationType.setImageResource(R.drawable.ic_promotion) } + "Promotions" -> { + binding.ivNotificationType.setImageResource(R.drawable.ic_promotion) + } "Warning" -> { binding.ivNotificationType.setImageResource(R.drawable.ic_warning) } diff --git a/app/src/main/java/com/kom/skyfly/presentation/register/RegisterActivity.kt b/app/src/main/java/com/kom/skyfly/presentation/register/RegisterActivity.kt index 05452c7..a9e6297 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/register/RegisterActivity.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/register/RegisterActivity.kt @@ -50,8 +50,9 @@ class RegisterActivity : AppCompatActivity() { private fun openOtpModal( token: String?, email: String?, + phoneNumber: String?, ) { - val verifyOtpFragment = VerifyOtpFragment.newInstance(token, email) + val verifyOtpFragment = VerifyOtpFragment.newInstance(token, email, phoneNumber) verifyOtpFragment.show(supportFragmentManager, verifyOtpFragment.tag) } @@ -77,26 +78,25 @@ class RegisterActivity : AppCompatActivity() { result.proceedWhen( doOnSuccess = { binding.pbLoading.isVisible = false - binding.btnRegister.isVisible = true + binding.btnRegister.isEnabled = true result.payload?.let { val token = it.token - openOtpModal(token, email) + openOtpModal(token, email, phoneNumber) } }, doOnError = { binding.pbLoading.isVisible = false - binding.btnRegister.isVisible = true + binding.btnRegister.isEnabled = true if (it.exception is NoInternetException) { binding.csvRegister.setState( ContentState.ERROR_NETWORK_GENERAL, - "Tidak ada internet!", + "No Internet Connection!", ) } else { Toasty.error( this, getString( R.string.text_register_failed, - it.exception?.message.orEmpty(), ), Toast.LENGTH_SHORT, true, @@ -105,7 +105,7 @@ class RegisterActivity : AppCompatActivity() { }, doOnLoading = { binding.pbLoading.isVisible = true - binding.btnRegister.isVisible = false + binding.btnRegister.isEnabled = false }, ) } @@ -118,7 +118,7 @@ class RegisterActivity : AppCompatActivity() { tilNoTlp.isVisible = true tilPassword.isVisible = true tilConfirmPassword.isVisible = true - etNoTlp.setText("62") + etNoTlp.setText(getString(R.string.default_phone_number_value)) } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpFragment.kt b/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpFragment.kt index d454060..fd5a520 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpFragment.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpFragment.kt @@ -22,19 +22,24 @@ class VerifyOtpFragment : BottomSheetDialogFragment() { private lateinit var binding: FragmentVerifyOtpBinding private val verifyOtpViewModel: VerifyOtpViewModel by viewModel() private lateinit var token: String + private lateinit var phoneNumber: String + private var countdownTimer: CountDownTimer? = null companion object { private const val ARG_TOKEN = "arg_token" private const val ARG_EMAIL = "arg_email" + private const val ARG_PHONE_NUMBER = "arg_phone_number" fun newInstance( token: String?, email: String?, + phoneNumber: String?, ): VerifyOtpFragment { val fragment = VerifyOtpFragment() val args = Bundle() args.putString(ARG_TOKEN, token) args.putString(ARG_EMAIL, email) + args.putString(ARG_PHONE_NUMBER, phoneNumber) fragment.arguments = args return fragment } @@ -55,14 +60,36 @@ class VerifyOtpFragment : BottomSheetDialogFragment() { ) { super.onViewCreated(view, savedInstanceState) startCountdownTimer() + setOnClickListeners() getOtpViewValue() val email = arguments?.getString(ARG_EMAIL).toString() - binding.tvEmailValue.text = email + phoneNumber = arguments?.getString(ARG_PHONE_NUMBER).toString() + token = arguments?.getString(ARG_TOKEN).toString() + binding.tvEmailValue.text = maskEmail(email) + } + + private fun maskEmail(email: String): String { + val atIndex = email.indexOf('@') + if (atIndex <= 1) { + return email + } + val maskedPart = "*".repeat(atIndex - 1) + return email[0] + maskedPart + email.substring(atIndex - 3) + } + + private fun setOnClickListeners() { + binding.tvResendOtp.setOnClickListener { + requestResendOtp(token) + startCountdownTimer() + } + binding.tvReqNewOtp.setOnClickListener { + requestResendOtpSms(token, phoneNumber) + startCountdownTimer() + } } private fun getOtpViewValue() { val otpView = binding.otpView - token = arguments?.getString(ARG_TOKEN).toString() otpView.setOtpCompletionListener { otp -> Log.d("Actual Value", otp) @@ -70,9 +97,6 @@ class VerifyOtpFragment : BottomSheetDialogFragment() { verifyProceed(token, otp) } } - binding.tvResendOtp.setOnClickListener { - requestResendOtp(token) - } } private fun verifyProceed( @@ -105,14 +129,12 @@ class VerifyOtpFragment : BottomSheetDialogFragment() { } private fun requestResendOtp(token: String) { - Log.d("VerifyOtpFragment", "Requesting resend OTP for token: $token") verifyOtpViewModel.resendOtpRequest(token).observe(viewLifecycleOwner) { result -> result.proceedWhen( doOnSuccess = { - Log.d("VerifyOtpFragment", "Resend OTP success: ${it.message}") Toast.makeText( requireContext(), - "OTP dikirim ke email!", + getString(R.string.text_otp_has_been_sent_to_your_email), Toast.LENGTH_SHORT, ).show() it.payload?.let { @@ -129,6 +151,35 @@ class VerifyOtpFragment : BottomSheetDialogFragment() { } } + private fun requestResendOtpSms( + token: String, + phoneNumber: String, + ) { + Log.d("VerifyOtpFragment", "Requesting resend OTP for token: $token") + verifyOtpViewModel.resendOtpSmsRequest(token, phoneNumber) + .observe(viewLifecycleOwner) { result -> + result.proceedWhen( + doOnSuccess = { + Log.d("VerifyOtpFragment", "Resend OTP success: ${it.message}") + Toast.makeText( + requireContext(), + getString(R.string.text_otp_has_been_sent_to_your_sms), + Toast.LENGTH_SHORT, + ).show() + it.payload?.let { + val newToken = it.token + if (newToken != null) { + this@VerifyOtpFragment.token = newToken + } + } + }, + doOnError = { + Log.d("resendOtpError", "requestResendOtp: Error ${it.exception?.message}") + }, + ) + } + } + private fun navigateToLogin() { startActivity( Intent(requireActivity(), LoginActivity::class.java).apply { @@ -138,19 +189,30 @@ class VerifyOtpFragment : BottomSheetDialogFragment() { } private fun startCountdownTimer() { - val countdownTimer = + countdownTimer?.cancel() + + countdownTimer = object : CountDownTimer(60000, 1000) { @SuppressLint("StringFormatMatches") override fun onTick(millisUntilFinished: Long) { - binding.tvResendOtpCoundown.text = - getString(R.string.text_countdown_otp, millisUntilFinished / 1000) + if (isAdded) { + binding.tvResendOtpCoundown.text = + getString(R.string.text_countdown_otp, millisUntilFinished / 1000) + } } override fun onFinish() { - binding.tvResendOtp.isVisible = true - binding.tvResendOtpCoundown.isVisible = false + if (isAdded) { + binding.tvResendOtp.isVisible = true + binding.tvResendOtpCoundown.isVisible = false + } } } - countdownTimer.start() + countdownTimer?.start() + } + + override fun onDestroyView() { + super.onDestroyView() + countdownTimer?.cancel() } } diff --git a/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpViewModel.kt b/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpViewModel.kt index f1c3bab..a2ffdeb 100644 --- a/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpViewModel.kt +++ b/app/src/main/java/com/kom/skyfly/presentation/verifyotp/VerifyOtpViewModel.kt @@ -16,4 +16,9 @@ class VerifyOtpViewModel(private val authRepository: AuthRepository) : ViewModel ) = authRepository.doVerifyAccount(token, otp).asLiveData(Dispatchers.IO) fun resendOtpRequest(token: String) = authRepository.resendOtpRequest(token).asLiveData(Dispatchers.IO) + + fun resendOtpSmsRequest( + token: String, + phoneNumber: String, + ) = authRepository.resendOtpSmsRequest(token, phoneNumber).asLiveData(Dispatchers.IO) } diff --git a/app/src/main/java/com/kom/skyfly/utils/ResultWrapper.kt b/app/src/main/java/com/kom/skyfly/utils/ResultWrapper.kt index 31b3bb6..57fd954 100644 --- a/app/src/main/java/com/kom/skyfly/utils/ResultWrapper.kt +++ b/app/src/main/java/com/kom/skyfly/utils/ResultWrapper.kt @@ -4,10 +4,16 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.onStart +import retrofit2.HttpException import java.io.IOException +import java.net.HttpURLConnection class NoInternetException() : IOException() +class UnAuthorizeException() : Exception() + +class ServerErrorException() : Exception() + sealed class ResultWrapper( val payload: T? = null, val message: String? = null, @@ -88,10 +94,23 @@ fun proceedFlow(block: suspend () -> T): Flow> { } fun Throwable?.parseException(): Exception { - when (this) { + return when (this) { is IOException -> { - return NoInternetException() + NoInternetException() + } + + is HttpException -> { + if (this.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + UnAuthorizeException() + } else if (this.code() == HttpURLConnection.HTTP_INTERNAL_ERROR) { + ServerErrorException() + } else { + Exception(this) + } + } + + else -> { + Exception(this) } - else -> return Exception(this) } } diff --git a/app/src/main/java/com/kom/skyfly/utils/SetVisibilityExt.kt b/app/src/main/java/com/kom/skyfly/utils/SetVisibilityExt.kt new file mode 100644 index 0000000..4d84e4d --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/utils/SetVisibilityExt.kt @@ -0,0 +1,7 @@ +package com.kom.skyfly.utils + +import android.view.View + +fun View.setVisible(isVisible: Boolean) { + visibility = if (isVisible) View.VISIBLE else View.GONE +} diff --git a/app/src/main/java/com/kom/skyfly/utils/TextWatcherExt.kt b/app/src/main/java/com/kom/skyfly/utils/TextWatcherExt.kt new file mode 100644 index 0000000..a651be5 --- /dev/null +++ b/app/src/main/java/com/kom/skyfly/utils/TextWatcherExt.kt @@ -0,0 +1,31 @@ +package com.kom.skyfly.utils + +import android.text.Editable +import android.text.TextWatcher +import android.widget.TextView + +fun TextView.afterTextChanged(afterTextChanged: (String) -> Unit) { + this.addTextChangedListener( + object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int, + ) { + } + + override fun onTextChanged( + s: CharSequence?, + start: Int, + before: Int, + count: Int, + ) { + } + + override fun afterTextChanged(editable: Editable?) { + afterTextChanged.invoke(editable.toString()) + } + }, + ) +} diff --git a/app/src/main/res/layout/activity_checkout_ticket.xml b/app/src/main/res/layout/activity_checkout_ticket.xml index 54fded1..5783290 100644 --- a/app/src/main/res/layout/activity_checkout_ticket.xml +++ b/app/src/main/res/layout/activity_checkout_ticket.xml @@ -1,5 +1,5 @@ - - + android:layout_height="match_parent"> - + - + - + + + android:layout_marginHorizontal="8dp" + android:layout_marginVertical="16dp" + android:visibility="gone" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/layout_header" + tools:visibility="visible"> - + android:layout_height="wrap_content"> - + - + - + - + + + + android:layout_height="0dp" + android:layout_margin="8dp" + app:layout_constraintBottom_toTopOf="@id/cv_btn_proceed_to_payment" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/layout_header"> -