diff --git a/MyApplication/.gitignore b/MyApplication/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/MyApplication/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/MyApplication/.idea/.gitignore b/MyApplication/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/MyApplication/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/MyApplication/.idea/.name b/MyApplication/.idea/.name new file mode 100644 index 0000000..b3405b3 --- /dev/null +++ b/MyApplication/.idea/.name @@ -0,0 +1 @@ +My Application \ No newline at end of file diff --git a/MyApplication/.idea/compiler.xml b/MyApplication/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/MyApplication/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/deploymentTargetDropDown.xml b/MyApplication/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..0c0c338 --- /dev/null +++ b/MyApplication/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/deploymentTargetSelector.xml b/MyApplication/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/MyApplication/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/gradle.xml b/MyApplication/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/MyApplication/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/inspectionProfiles/Project_Default.xml b/MyApplication/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7ea997a --- /dev/null +++ b/MyApplication/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/MyApplication/.idea/kotlinc.xml b/MyApplication/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/MyApplication/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/migrations.xml b/MyApplication/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/MyApplication/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/misc.xml b/MyApplication/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/MyApplication/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/other.xml b/MyApplication/.idea/other.xml new file mode 100644 index 0000000..1b1a2c5 --- /dev/null +++ b/MyApplication/.idea/other.xml @@ -0,0 +1,296 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/.idea/vcs.xml b/MyApplication/.idea/vcs.xml new file mode 100644 index 0000000..288b36b --- /dev/null +++ b/MyApplication/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.gitignore b/MyApplication/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/MyApplication/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/MyApplication/app/.idea/gradle.xml b/MyApplication/app/.idea/gradle.xml new file mode 100644 index 0000000..89935b5 --- /dev/null +++ b/MyApplication/app/.idea/gradle.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.idea/migrations.xml b/MyApplication/app/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/MyApplication/app/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.idea/misc.xml b/MyApplication/app/.idea/misc.xml new file mode 100644 index 0000000..3040d03 --- /dev/null +++ b/MyApplication/app/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.idea/other.xml b/MyApplication/app/.idea/other.xml new file mode 100644 index 0000000..1b1a2c5 --- /dev/null +++ b/MyApplication/app/.idea/other.xml @@ -0,0 +1,296 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.idea/vcs.xml b/MyApplication/app/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/MyApplication/app/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/.idea/workspace.xml b/MyApplication/app/.idea/workspace.xml new file mode 100644 index 0000000..14b9182 --- /dev/null +++ b/MyApplication/app/.idea/workspace.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1724649643260 + + + + \ No newline at end of file diff --git a/MyApplication/app/build.gradle.kts b/MyApplication/app/build.gradle.kts new file mode 100644 index 0000000..2bf87f3 --- /dev/null +++ b/MyApplication/app/build.gradle.kts @@ -0,0 +1,74 @@ +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.jetbrainsKotlinAndroid) + alias(libs.plugins.googleAndroidLibrariesMapsplatformSecretsGradlePlugin) +} + +android { + namespace = "com.example.myapplication" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.myapplication" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + viewBinding = true + } +} + +dependencies { + implementation("com.github.bumptech.glide:glide:4.15.1") + annotationProcessor("com.github.bumptech.glide:compiler:4.15.1") + implementation("androidx.navigation:navigation-fragment-ktx:2.7.1") + implementation("androidx.navigation:navigation-ui-ktx:2.7.1") + implementation("com.google.maps.android:android-maps-utils:2.3.0") + implementation("com.google.android.gms:play-services-maps:18.0.2") + implementation("com.google.android.gms:play-services-location:20.0.0") + implementation("com.squareup.okhttp3:okhttp:4.9.3") + implementation("com.github.kittinunf.fuel:fuel:2.3.1") + implementation("com.google.android.material:material:1.8.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:okhttp:4.9.3") + implementation("com.google.code.gson:gson:2.8.9") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.3") + implementation("com.google.android.gms:play-services-maps:18.0.0") + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.material) + implementation(libs.androidx.activity) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.lifecycle.livedata.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.androidx.fragment.ktx) + implementation(libs.androidx.legacy.support.v4) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) +} \ No newline at end of file diff --git a/MyApplication/app/proguard-rules.pro b/MyApplication/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/MyApplication/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/MyApplication/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt b/MyApplication/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..e9283cf --- /dev/null +++ b/MyApplication/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.myapplication + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.myapplication", appContext.packageName) + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/AndroidManifest.xml b/MyApplication/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..90a4842 --- /dev/null +++ b/MyApplication/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/adapter/BusAdapter.kt b/MyApplication/app/src/main/java/com/example/myapplication/adapter/BusAdapter.kt new file mode 100644 index 0000000..f10feee --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/adapter/BusAdapter.kt @@ -0,0 +1,50 @@ +package com.example.myapplication.adapter + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.databinding.BusObjectBinding +import com.example.myapplication.model.BusLineVehicle + +class BusAdapter(requireContext: Context) : + ListAdapter(BusDiffCallback()) { + + class ViewHolder(private val binding: BusObjectBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(busVehicle: BusLineVehicle) { + with(binding) { + busCodeName.text = busVehicle.line.lineSign + destiny.text = busVehicle.line.destination + busPrefix.text = "Prefixo: ${busVehicle.vehicle.busPrefix}" + arriveTime.text = busVehicle.vehicle.arriveTime + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = BusObjectBinding.inflate(inflater, parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val vehicle = getItem(position) + holder.bind(vehicle) + } + + private class BusDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: BusLineVehicle, newItem: BusLineVehicle): Boolean { + // Verifique se há um identificador único para comparar os itens + return oldItem.vehicle.busPrefix == newItem.vehicle.busPrefix + } + + override fun areContentsTheSame(oldItem: BusLineVehicle, newItem: BusLineVehicle): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/adapter/LineAdapter.kt b/MyApplication/app/src/main/java/com/example/myapplication/adapter/LineAdapter.kt new file mode 100644 index 0000000..47a7db1 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/adapter/LineAdapter.kt @@ -0,0 +1,75 @@ +package com.example.myapplication.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.databinding.LinesObjectBinding + +class LineAdapter( + private val context: Context, + private var busVehicle: MutableList = mutableListOf(), + private val onItemClick: (ListItem) -> Unit +) : RecyclerView.Adapter() { + + class ViewHolder(private val binding: LinesObjectBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(listItem: ListItem, onItemClick: (ListItem) -> Unit) { + when (listItem) { + is ListItem.BusLine -> { + binding.busCodeName.text = listItem.vehicle.line.lineSign + binding.destiny.text = "Destino: ${listItem.vehicle.line.destination}" + binding.origin.text = "Origem: ${listItem.vehicle.line.origin}" + binding.quantity.text = "Quantidade: ${listItem.vehicle.line.vehicleCount} veículos" + } + is ListItem.Line -> { + binding.busCodeName.text = "Código: ${listItem.line.lineCode}" + binding.destiny.text = "Destino: ${listItem.line.destinationTowardsPrimary}" + binding.origin.text = "Origem: ${listItem.line.destinationTowardsSecondary}" + binding.quantity.text = "Letreiro: ${listItem.line.lineNumberPrefix}-${listItem.line.lineNumberSuffix}" + } + } + + binding.root.setOnClickListener { onItemClick(listItem) } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = LinesObjectBinding.inflate(inflater, parent, false) + return ViewHolder(binding) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val vehicle = busVehicle[position] + holder.bind(vehicle, onItemClick) + } + + override fun getItemCount(): Int = busVehicle.size + + fun setData(newList: List) { + val diffCallback = LineDiffCallback(busVehicle, newList) + val diffResult = DiffUtil.calculateDiff(diffCallback) + busVehicle.clear() + busVehicle.addAll(newList) + diffResult.dispatchUpdatesTo(this) + } +} + +class LineDiffCallback( + private val oldList: List, + private val newList: List +) : DiffUtil.Callback() { + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/adapter/ListItem.kt b/MyApplication/app/src/main/java/com/example/myapplication/adapter/ListItem.kt new file mode 100644 index 0000000..727f205 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/adapter/ListItem.kt @@ -0,0 +1,9 @@ +package com.example.myapplication.adapter + +import com.example.myapplication.model.BusLineVehicle +import com.example.myapplication.model.Lines + +sealed class ListItem { + data class BusLine(val vehicle: BusLineVehicle) : ListItem() + data class Line(val line: Lines) : ListItem() +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/infra/ApiService.kt b/MyApplication/app/src/main/java/com/example/myapplication/infra/ApiService.kt new file mode 100644 index 0000000..9ef1d23 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/infra/ApiService.kt @@ -0,0 +1,32 @@ +package com.example.myapplication.infra + +import com.example.myapplication.model.BusArriveResponse +import com.example.myapplication.model.BusStop +import com.example.myapplication.model.BusStopSearch +import com.example.myapplication.model.Lines +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.POST + +import retrofit2.http.Query + +interface ApiService { + + + @POST("Login/Autenticar") + suspend fun auth(@Query("token")token: String): Boolean + @GET("Linha/Buscar") + suspend fun searchBusLine(@Header("Authorization") token: String, @Query("termosBusca") termoBusca: String):List + + @GET("Parada/Buscar?termosBusca=") + suspend fun getAllBusStop(): List + + @GET("Parada/Buscar") + suspend fun getBusStopByNameOrAddress(@Header("Authorization") token: String, @Query("termosBusca") termosBusca: String): List + + @GET("Parada/BuscarParadasPorLinha") + suspend fun getStopsByLine(@Query("codigoLinha") lineCode: Int): List + + @GET("Previsao/Parada") + suspend fun getBusByBusStopCode(@Header("Authorization") token: String, @Query("codigoParada") codigoParada: String):BusArriveResponse +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/infra/Retrofit.kt b/MyApplication/app/src/main/java/com/example/myapplication/infra/Retrofit.kt new file mode 100644 index 0000000..c90e1f7 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/infra/Retrofit.kt @@ -0,0 +1,27 @@ +package com.example.myapplication.infra + +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +class Retrofit { + + private val loginInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + private val client = OkHttpClient.Builder() + .addInterceptor(loginInterceptor) + .build() + + private val retrofit = Retrofit.Builder() + .baseUrl("https://aiko-olhovivo-proxy.aikodigital.io/") + .addConverterFactory(GsonConverterFactory.create()) + .client(client) + .build() + + val apiService: ApiService = retrofit.create(ApiService::class.java) + + +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/infra/Token.kt b/MyApplication/app/src/main/java/com/example/myapplication/infra/Token.kt new file mode 100644 index 0000000..ae1505d --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/infra/Token.kt @@ -0,0 +1,8 @@ +package com.example.myapplication.infra + +object Token { + object valueApi { + const val TOKEN ="40ce0f375312ea66af02724dbb7de15a6dba1a96a7f6415dfd724b07df5e3d34" + } +} + diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/BusArriveResponse.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/BusArriveResponse.kt new file mode 100644 index 0000000..cda9a20 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/BusArriveResponse.kt @@ -0,0 +1,7 @@ +package com.example.myapplication.model + +import com.google.gson.annotations.SerializedName + +data class BusArriveResponse( + @SerializedName("p") val busStop: BusStop +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/BusLine.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/BusLine.kt new file mode 100644 index 0000000..392a072 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/BusLine.kt @@ -0,0 +1,13 @@ +package com.example.myapplication.model + +import com.google.gson.annotations.SerializedName + +data class BusLine( + @SerializedName("c") val lineSign: String, + @SerializedName("cl") val lineCode: String, + @SerializedName("sl") val direction: Int, + @SerializedName("lt0") val destination: String, + @SerializedName("lt1") val origin: String, + @SerializedName("qv") val vehicleCount: Int, + @SerializedName("vs") val vehicles: List +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/BusLineVehicle.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/BusLineVehicle.kt new file mode 100644 index 0000000..5a201b9 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/BusLineVehicle.kt @@ -0,0 +1,6 @@ +package com.example.myapplication.model + +data class BusLineVehicle( + val line: BusLine, + val vehicle: BusVehicle +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/BusStop.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/BusStop.kt new file mode 100644 index 0000000..9427cfa --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/BusStop.kt @@ -0,0 +1,14 @@ +package com.example.myapplication.model + +import com.google.gson.annotations.SerializedName + +data class BusStop( + @SerializedName("cp") val stopCode: String, + @SerializedName("np") val stopName: String, + @SerializedName("ed") val stopLocation: String, + @SerializedName("py") val lat: Double, + @SerializedName("px") val long: Double, + @SerializedName("l") val line: List + + +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/BusStopSearch.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/BusStopSearch.kt new file mode 100644 index 0000000..81dd18d --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/BusStopSearch.kt @@ -0,0 +1,11 @@ +package com.example.myapplication.model + +import com.google.gson.annotations.SerializedName + +data class BusStopSearch( + @SerializedName("cp") val stopCode: Int, + @SerializedName("np") val stopName: String?, + @SerializedName("ed") val stopLocation: String, + @SerializedName("py") val lat: Double, + @SerializedName("px") val long: Double +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/BusVehicle.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/BusVehicle.kt new file mode 100644 index 0000000..a5ac91f --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/BusVehicle.kt @@ -0,0 +1,15 @@ +package com.example.myapplication.model + +import com.google.gson.annotations.SerializedName +import java.util.Date + +data class BusVehicle( + @SerializedName("p") val busPrefix: String, + @SerializedName("t") val arriveTime: String, + @SerializedName("a") val active: Boolean, + @SerializedName("ta") val updateTime: String, + @SerializedName("py") val lat: Double, + @SerializedName("px") val long: Double, + @SerializedName("sv") val sv: String?, + @SerializedName("is") val ls: String? +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/model/Lines.kt b/MyApplication/app/src/main/java/com/example/myapplication/model/Lines.kt new file mode 100644 index 0000000..ab4d14e --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/model/Lines.kt @@ -0,0 +1,13 @@ +package com.example.myapplication.model + +import com.google.gson.annotations.SerializedName + +data class Lines( + @SerializedName("cl") val lineCode: String, + @SerializedName("lc") val isCircular: Boolean, + @SerializedName("lt") val lineNumberPrefix: String, + @SerializedName("sl") val direction: String, + @SerializedName("tl") val lineNumberSuffix: String, + @SerializedName("tp") val destinationTowardsPrimary: String, + @SerializedName("ts") val destinationTowardsSecondary: String +) diff --git a/MyApplication/app/src/main/java/com/example/myapplication/view/LineFragment.kt b/MyApplication/app/src/main/java/com/example/myapplication/view/LineFragment.kt new file mode 100644 index 0000000..c5f6f10 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/view/LineFragment.kt @@ -0,0 +1,154 @@ +package com.example.myapplication.view + +import android.Manifest +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import com.bumptech.glide.Glide +import com.example.myapplication.R +import com.example.myapplication.adapter.LineAdapter +import com.example.myapplication.adapter.ListItem +import com.example.myapplication.databinding.FragmentLineBinding +import com.example.myapplication.viewModel.LineViewModel +import com.google.android.gms.maps.SupportMapFragment +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.delay + +class LineFragment : Fragment() { + + private lateinit var binding: FragmentLineBinding + private lateinit var lineAdapter: LineAdapter + private lateinit var requestPermissionLauncher: ActivityResultLauncher + + private val viewModel: LineViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestPermissionLauncher = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { isGranted: Boolean -> + if (isGranted) { + initializeMap() + } else { + showPermissionRationale() + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentLineBinding.inflate(inflater, container, false) + + if (ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + initializeMap() + } else { + requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) + } + + Glide.with(requireContext()) + .asGif() + .load(R.drawable.onibus) + .into(binding.imageView) + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initializeAdapters() + configRv() + searchLine() + observeViewModel() + } + + private fun initializeAdapters() { + lineAdapter = LineAdapter(requireContext(), mutableListOf()) { listItem -> + val lineCode = (listItem as? ListItem.BusLine)?.vehicle?.line?.lineCode + ?: (listItem as? ListItem.Line)?.line?.lineCode + + lineCode?.toIntOrNull()?.let { code -> + navigateToMapFragment(code) + } ?: Toast.makeText(requireContext(), "Código da linha inválido", Toast.LENGTH_SHORT).show() + } + } + + private fun configRv() { + binding.rv.apply { + adapter = lineAdapter + layoutManager = LinearLayoutManager(requireContext()) + } + } + + private fun searchLine() { + binding.searchBtn.setOnClickListener { + val search = binding.searchInput.text.toString() + lifecycleScope.launch(Dispatchers.IO) { + viewModel.fetchBusLine(search) + } + binding.informationContainer.visibility = View.GONE + binding.rv.visibility = View.VISIBLE + } + } + + private fun observeViewModel() { + viewModel.lineList.observe(viewLifecycleOwner, Observer { lines -> + val linesMutable = lines.map { ListItem.Line(it) } + lineAdapter.setData(linesMutable) + }) + } + + private fun navigateToMapFragment(lineCode: Int) { + val mapFragment = MapFragment.newInstance() + parentFragmentManager.beginTransaction() + .replace(R.id.mainContainer, mapFragment) + .addToBackStack(null) + .commitAllowingStateLoss() + + parentFragmentManager.executePendingTransactions() + + lifecycleScope.launch { + delay(100) // Ajuste o delay conforme necessário + val fragment = parentFragmentManager.findFragmentById(R.id.mainContainer) as? MapFragment + fragment?.updateMapWithLineCode(lineCode) + } + } + + private fun showPermissionRationale() { + AlertDialog.Builder(requireContext()) + .setTitle("Permissão de Localização Necessária") + .setMessage("Para melhor uso do app, disponibilize sua localização.") + .setPositiveButton("OK") { _, _ -> + requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) + } + .setNegativeButton("Cancelar") { dialog, _ -> + dialog.dismiss() + } + .show() + } + + private fun initializeMap() { + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as? SupportMapFragment + mapFragment?.getMapAsync { googleMap -> + // Configurações do mapa aqui + } + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/view/MainActivity.kt b/MyApplication/app/src/main/java/com/example/myapplication/view/MainActivity.kt new file mode 100644 index 0000000..3b122b5 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/view/MainActivity.kt @@ -0,0 +1,70 @@ +package com.example.myapplication.view + +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.View +import android.widget.ProgressBar +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.example.myapplication.R +import com.example.myapplication.databinding.ActivityMainBinding +import com.example.myapplication.infra.Retrofit +import com.example.myapplication.infra.Token +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class MainActivity : AppCompatActivity() { + + private lateinit var binding: ActivityMainBinding + private val mapFragment by lazy { MapFragment() } + private val lineFragment by lazy { LineFragment() } + private lateinit var progressBar: ProgressBar + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + progressBar = findViewById(R.id.progress_bar) + + authenticateUser() + setupBottomNavigation() + loadDefaultFragment() + } + + private fun authenticateUser() { + CoroutineScope(Dispatchers.IO).launch { + Retrofit().apiService.auth(Token.valueApi.TOKEN) + } + } + + private fun setupBottomNavigation() { + binding.bottomNavigation.setOnItemSelectedListener { item -> + val fragment = when (item.itemId) { + R.id.lines -> lineFragment + R.id.busStop -> mapFragment + else -> null + } + fragment?.let { changeFragment(it) } + fragment != null + } + } + + private fun loadDefaultFragment() { + changeFragment(lineFragment) + } + + private fun changeFragment(fragment: Fragment) { + progressBar.visibility = View.VISIBLE + + supportFragmentManager.beginTransaction() + .replace(R.id.mainContainer, fragment) + .commit() + + Handler(Looper.getMainLooper()).postDelayed({ + progressBar.visibility = View.GONE + }, 500) + } +} + diff --git a/MyApplication/app/src/main/java/com/example/myapplication/view/MapFragment.kt b/MyApplication/app/src/main/java/com/example/myapplication/view/MapFragment.kt new file mode 100644 index 0000000..37c1996 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/view/MapFragment.kt @@ -0,0 +1,571 @@ +package com.example.myapplication.view + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.myapplication.R +import com.example.myapplication.adapter.LineAdapter +import com.example.myapplication.adapter.ListItem +import com.example.myapplication.databinding.FragmentMapBinding +import com.example.myapplication.model.BusLineVehicle +import com.example.myapplication.model.BusStop +import com.example.myapplication.model.BusStopSearch +import com.example.myapplication.viewModel.MapViewModel +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.material.bottomsheet.BottomSheetBehavior +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.Locale +import android.location.Geocoder +import android.os.Handler +import android.os.Looper +import androidx.lifecycle.lifecycleScope +import com.example.myapplication.adapter.BusAdapter +import com.example.myapplication.infra.Token +import com.example.myapplication.viewModel.LineViewModel +import com.google.android.gms.maps.model.PolylineOptions +import kotlinx.coroutines.withContext + + +class MapFragment : Fragment() { + companion object { + fun newInstance(): MapFragment { + return MapFragment() + } + } + + private lateinit var binding: FragmentMapBinding + private lateinit var mMap: GoogleMap + private lateinit var editText: EditText + private lateinit var bottomSheetBehavior: BottomSheetBehavior + private val viewModel: MapViewModel by viewModels() + private val viewModelLine: LineViewModel by viewModels() + private lateinit var recyclerView: RecyclerView + + private var num = 0 + private var ruaIndex = 0 + private var charIndex = 0 + private lateinit var icon: BitmapDescriptor + private val busMarkers = mutableMapOf() + private var busLineVehicle: MutableList = mutableListOf() + private var busStopList: List = emptyList() + private lateinit var busAdapter: BusAdapter + private lateinit var lineAdapter: LineAdapter + private var stopList: List = emptyList() + + private val ruas = listOf( + "Avenida Paulista", + "Rua Oscar Freire", + "Avenida Ibirapuera", + "Rua 25 de Março" + ) + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentMapBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // Chame a função suspend dentro de uma coroutine + viewLifecycleOwner.lifecycleScope.launch { + initializeAdapters() + } + + configureBottomSheet() + setupMap() + setupHintUpdate() + initSearchContainer() + observeViewModel() + + // Inicialize e configure o RecyclerView para busAdapter + busAdapter = BusAdapter(requireContext()) + binding.rv.apply { + adapter = busAdapter + layoutManager = LinearLayoutManager(requireContext()) + } + + // Observa as mudanças no ViewModel e atualiza o busAdapter + viewModelLine.busStops.observe(viewLifecycleOwner) { + busAdapter.submitList(busLineVehicle) + } + + // Configurar o listener para dimBackground + binding.dimBackground.setOnClickListener { + hideOptionsContainer() // Oculta o modal e limpa o fundo + } + + // Configurar o Floating Action Button + binding.fab.setOnClickListener { + // fetchAndDisplayBusStops(34041) + } + + // Inicializar e configurar o RecyclerView para lineAdapter + recyclerView = binding.rvLines + recyclerView.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = lineAdapter + } + + // Configurar o LineAdapter + lineAdapter = LineAdapter(requireContext()) { listItem -> + // Ação a ser executada quando um item for clicado + val lineCode = when (listItem) { + is ListItem.BusLine -> listItem.vehicle.line.lineCode + is ListItem.Line -> listItem.line.lineCode + } + val lineCodeInt = lineCode.toIntOrNull() + if (lineCodeInt != null) { + (parentFragment as? MapFragment)?.let { fragment -> + viewLifecycleOwner.lifecycleScope.launch { + fragment.updateMapWithLineCode(lineCodeInt) + } + } + } + } + + // Certifique-se de que o adapter está definido para o RecyclerView + recyclerView.adapter = lineAdapter + } + + + private fun showOptionsContainer() { + binding.dimBackground.visibility = View.VISIBLE + binding.optionsContainer.visibility = View.VISIBLE + binding.dimBackground.alpha = 0f + binding.optionsContainer.alpha = 0f + binding.dimBackground.animate().alpha(0.5f).setDuration(200) + binding.optionsContainer.animate().alpha(1f).setDuration(200) + } + + private fun hideOptionsContainer() { + binding.dimBackground.animate().alpha(0f).setDuration(200).withEndAction { + binding.dimBackground.visibility = View.GONE + } + binding.optionsContainer.animate().alpha(0f).setDuration(200).withEndAction { + binding.optionsContainer.visibility = View.GONE + } + } + + fun initializeAdapters() { + lineAdapter = LineAdapter(requireContext(), mutableListOf()) { listItem -> + val lineCode = when (listItem) { + is ListItem.BusLine -> listItem.vehicle.line.lineCode + is ListItem.Line -> listItem.line.lineCode + } + + // Converta o lineCode para Int + val lineCodeInt = lineCode.toIntOrNull() ?: run { + // Caso a conversão falhe, mostre um erro ou trate a falha + Toast.makeText(requireContext(), "Código da linha inválido", Toast.LENGTH_SHORT) + .show() + return@LineAdapter + } + + // Inicia uma coroutine para chamar a função suspend + viewLifecycleOwner.lifecycleScope.launch { + (parentFragment as? MapFragment)?.updateMapWithLineCode(lineCodeInt) + } + } + } + + private fun setupMap() { + val mapFragment = + childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync { googleMap -> + mMap = googleMap + initializeMap() + fetchBusStop() + setupMapListeners() + } + } + + private fun initializeMap() { + // Coordenadas do Terminal de Ônibus da Barra Funda + val barraFunda = LatLng(-23.534426, -46.638737) + + mMap.addMarker( + MarkerOptions() + .position(barraFunda) + .title("Terminal de Ônibus da Barra Funda") + ) + + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(barraFunda, 15f)) + } + + @SuppressLint("PotentialBehaviorOverride") + private fun setupMapListeners() { + mMap.setOnMapClickListener { + bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + + mMap.setOnMarkerClickListener { clickedMarker -> + handleMarkerClick(clickedMarker) + true + } + } + + private fun handleMarkerClick(clickedMarker: Marker) { + val clickedBusStop = clickedMarker.getBusStop() + val busInfo = clickedMarker.getBusMarker() + + clickedBusStop?.let { + CoroutineScope(Dispatchers.IO).launch { + viewModel.fetchBusArriveTime(it.stopCode) + } + updateUIForBusStop(it) + } + + busInfo?.let { + showBusDialog(it) + } + } + + + private fun updateUIForBusStop(busStop: BusStop) { + configureRecyclerView(busAdapter, binding.rv) + configureRecyclerView(lineAdapter, binding.rvLines) + + val stopNameText = getString(R.string.bus_stop_name, busStop.stopName) + val stopAddressText = getString(R.string.bus_stop_address, busStop.stopLocation) + + binding.busStopName.text = stopNameText + binding.busStopAdress.text = stopAddressText + configureClickListeners(busStop) + num = 0 + } + + private fun configureRecyclerView( + adapter: RecyclerView.Adapter<*>, + recyclerView: RecyclerView + ) { + recyclerView.adapter = adapter + recyclerView.layoutManager = LinearLayoutManager(requireContext()) + } + + private fun fetchBusStop() { + CoroutineScope(Dispatchers.IO).launch { + try { + busStopList = viewModel.getBusStop() + activity?.runOnUiThread { + findBusStop(busStopList) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + private fun findBusStop(busStopList: List) { + icon = imageToBitmapDescriptor(R.drawable.busspoint) + + busStopList.forEach { busStop -> + val marker = mMap.addMarker( + MarkerOptions() + .position(LatLng(busStop.lat, busStop.long)) + .title(busStop.stopLocation) + .icon(icon) + ) + marker?.setBusStop(busStop) + } + } + + private fun imageToBitmapDescriptor(image: Int): BitmapDescriptor { + val bitmap = BitmapFactory.decodeResource(resources, image) + val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, false) + return BitmapDescriptorFactory.fromBitmap(scaledBitmap) + } + + private fun showBusInTheMap() { + val icon = imageToBitmapDescriptor(R.drawable.bus2) + + busLineVehicle.forEach { bl -> + val position = LatLng(bl.vehicle.lat, bl.vehicle.long) + val busPrefix = bl.vehicle.busPrefix + + if (busMarkers.containsKey(busPrefix)) { + busMarkers[busPrefix]?.position = position + } else { + val busInfo = mMap.addMarker( + MarkerOptions() + .position(position) + .title("Bus Prefix: $busPrefix") + .snippet("Additional Info: ${bl.vehicle.arriveTime}") + .icon(icon) + ) + busInfo?.let { + busMarkers[busPrefix] = it + it.setBusMarker(bl) + } + } + } + } + + private fun showBusDialog(busInfo: BusLineVehicle) { + AlertDialog.Builder(requireContext()) + .setTitle("Informações do ônibus") + .setMessage( + "Letreiro: ${busInfo.line.lineSign}\n" + + "Código da linha: ${busInfo.line.lineCode}\n" + + "Prefixo: ${busInfo.vehicle.busPrefix}\n" + + "Previsão de chegada: ${busInfo.vehicle.arriveTime}\n" + + "Destino: ${busInfo.line.destination}" + ) + .setPositiveButton("OK") { dialog, _ -> + dialog.dismiss() + } + .create() + .show() + } + + private fun configureClickListeners(busStop: BusStop) { + showOptionsContainer() + binding.busTime.setOnClickListener { + CoroutineScope(Dispatchers.IO).launch { + viewModel.fetchBusArriveTime(busStop.stopCode) + } + configureRecyclerView(busAdapter, binding.rv) + binding.rvLines.visibility = View.GONE + binding.rv.visibility = View.VISIBLE + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + } + + binding.linesRoute.setOnClickListener { + showOptionsContainer() + CoroutineScope(Dispatchers.IO).launch { + viewModel.fetchBusArriveTime(busStop.stopCode) + } + configureRecyclerView(lineAdapter, binding.rvLines) + binding.rv.visibility = View.GONE + binding.rvLines.visibility = View.VISIBLE + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + } + binding.fab.setOnClickListener { + busMarkers.values.forEach { it.remove() } + busMarkers.clear() + CoroutineScope(Dispatchers.IO).launch { + viewModel.fetchBusArriveTime(busStop.stopCode) + } + showBusInTheMap() + Toast.makeText(requireContext(), "Apresentação veículos próximos onde estavam na sua última atualização", Toast.LENGTH_SHORT).show() + } + } + private fun imageToBitMap(image: Int): BitmapDescriptor { + val bitmap = BitmapFactory.decodeResource(resources, image) + val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, false) + icon = BitmapDescriptorFactory.fromBitmap(scaledBitmap) + return icon + } + + private fun setupHintUpdate() { + editText = binding.editTextText + val handler = Handler(Looper.getMainLooper()) + val runnable = object : Runnable { + override fun run() { + val currentRua = ruas[ruaIndex] + if (charIndex < currentRua.length) { + editText.hint = currentRua.substring(0, charIndex + 1) + charIndex++ + handler.postDelayed(this, 300) + } else { + charIndex = 0 + ruaIndex = (ruaIndex + 1) % ruas.size + handler.postDelayed(this, 1000) + } + } + } + handler.post(runnable) + } + + private fun configureBottomSheet() { + bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomsheet) + bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + bottomSheetBehavior.peekHeight = 0 + binding.dimBackground.setOnClickListener { + hideOptionsContainer() + bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + } + + private fun initSearchContainer() { + binding.button.setOnClickListener { + val searchText = binding.editTextText.text.toString() + if (searchText.isNotEmpty()) { + search(searchText) + } + } + } + + private fun search(query: String) { + when (binding.searchTypeGroup.checkedRadioButtonId) { + R.id.radio_address -> searchLocation(query) + R.id.radio_bus_stop -> searchBusStop(query) + } + } + + private fun searchLocation(address: String) { + val geocoder = Geocoder(requireContext(), Locale.getDefault()) + CoroutineScope(Dispatchers.IO).launch { + try { + val addressList = geocoder.getFromLocationName(address, 1) + if (addressList?.isNotEmpty() == true) { + val location = addressList[0] + val latLng = LatLng(location.latitude, location.longitude) + withContext(Dispatchers.Main) { + mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 18f)) + mMap.addMarker(MarkerOptions().position(latLng).title(address)) + } + } else { + withContext(Dispatchers.Main) { + Toast.makeText( + requireContext(), + "Endereço não encontrado", + Toast.LENGTH_SHORT + ).show() + } + } + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + Toast.makeText(requireContext(), "Erro ao buscar endereço", Toast.LENGTH_SHORT) + .show() + } + } + } + } + + private fun searchBusStop(busStopName: String) { + CoroutineScope(Dispatchers.IO).launch { + try { + val authToken = Token + val busStopList = viewModel.apiService.getBusStopByNameOrAddress( + authToken.toString(), + busStopName + ) + + if (busStopList.isNotEmpty()) { + val busStop = busStopList[0] + val latLng = LatLng(busStop.lat, busStop.long) + withContext(Dispatchers.Main) { + mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 18f)) + mMap.addMarker(MarkerOptions().position(latLng).title(busStop.stopName)) + } + } else { + withContext(Dispatchers.Main) { + Toast.makeText( + requireContext(), + "Ponto de ônibus não encontrado", + Toast.LENGTH_SHORT + ).show() + } + } + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + Toast.makeText(requireContext(), "Erro de conexão", Toast.LENGTH_SHORT).show() + } + } + } + } + + private fun observeViewModel() { + viewModel.listOfBus.observe(viewLifecycleOwner, Observer { busList -> + busAdapter.submitList(busList) + val filterLine = busList.distinctBy { it.line.lineSign } + val lineList = filterLine.map { ListItem.BusLine(it) } + lineAdapter.setData(lineList) + busLineVehicle = busList + }) + } + + suspend fun updateMapWithLineCode(lineCode: Int) { + fetchAndDisplayBusStops(lineCode) + } + + private fun fetchAndDisplayBusStops(lineCode: Int) { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { + try { + val busStops = viewModel.apiService.getStopsByLine(lineCode) + withContext(Dispatchers.Main) { + displayBusStopsOnMap(busStops) + } + } catch (e: Exception) { + e.printStackTrace() + withContext(Dispatchers.Main) { + Toast.makeText(requireContext(), "Erro ao carregar paradas", Toast.LENGTH_SHORT) + .show() + } + } + } + } + + private fun displayBusStopsOnMap(busStops: List) { + val polylineOptions = PolylineOptions().apply { + color(Color.BLUE) + width(10f) + } + + + busStops.forEach { busStop -> + val position = LatLng(busStop.lat, busStop.long) + polylineOptions.add(position) + } + + if (polylineOptions.points.isNotEmpty()) { + mMap.addPolyline(polylineOptions) + } + + if (busStops.isNotEmpty()) { + val firstStop = busStops[0] + mMap.moveCamera( + CameraUpdateFactory.newLatLngZoom( + LatLng(firstStop.lat, firstStop.long), + 12f + ) + ) + } + } + + private fun Marker.setBusStop(busStop: BusStop) { + tag = busStop + } + + private fun Marker.getBusStop(): BusStop? { + return tag as? BusStop + } + + private fun Marker.setBusMarker(busStop: BusLineVehicle) { + tag = busStop + } + + private fun Marker.getBusMarker(): BusLineVehicle? { + return tag as? BusLineVehicle + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/viewModel/LineViewModel.kt b/MyApplication/app/src/main/java/com/example/myapplication/viewModel/LineViewModel.kt new file mode 100644 index 0000000..a8c06e3 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/viewModel/LineViewModel.kt @@ -0,0 +1,50 @@ +package com.example.myapplication.viewModel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.myapplication.infra.ApiService +import com.example.myapplication.infra.Retrofit +import com.example.myapplication.infra.Token +import com.example.myapplication.model.BusStop +import com.example.myapplication.model.Lines +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class LineViewModel : ViewModel() { + + private val _listOfLine = MutableLiveData>() + val lineList: LiveData> get() = _listOfLine + val apiService: ApiService = Retrofit().apiService + private val _busStops = MutableLiveData>() + val busStops: LiveData> get() = _busStops + + fun setBusStops(stops: List) { + _busStops.value = stops + } + init { + _listOfLine.value = mutableListOf() + } + + fun fetchBusLine(termoBusca: String){ + viewModelScope.launch { + val lineList = getLines(termoBusca) + _listOfLine.value = lineList.toMutableList() + } + } + + // Busca códigos de linhas de ônibus + private suspend fun getLines(termoBusca: String): List { + return withContext(Dispatchers.IO) { + try { + val response = Retrofit().apiService.searchBusLine("Bearer ${Token.valueApi.TOKEN}", termoBusca) + response + } catch (e: Exception) { + e.printStackTrace() + emptyList() + } + } + } +} \ No newline at end of file diff --git a/MyApplication/app/src/main/java/com/example/myapplication/viewModel/MapViewModel.kt b/MyApplication/app/src/main/java/com/example/myapplication/viewModel/MapViewModel.kt new file mode 100644 index 0000000..bb33ee3 --- /dev/null +++ b/MyApplication/app/src/main/java/com/example/myapplication/viewModel/MapViewModel.kt @@ -0,0 +1,85 @@ +package com.example.myapplication.viewModel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.myapplication.infra.ApiService +import com.example.myapplication.infra.Retrofit +import com.example.myapplication.infra.Token +import com.example.myapplication.model.BusLineVehicle +import com.example.myapplication.model.BusStop +import com.example.myapplication.model.BusStopSearch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class MapViewModel : ViewModel() { + val apiService: ApiService = Retrofit().apiService + private val _listOfBus = MutableLiveData>() + val listOfBus: LiveData> get() =_listOfBus + + private val AUTH_TOKEN =Token.valueApi.TOKEN + + + init { + _listOfBus.value = mutableListOf() + } + // Busca todas as paradas de ônibus + suspend fun getBusStop(): List { + val apiService = Retrofit().apiService + return withContext(Dispatchers.IO) { + try { + val response = apiService.getAllBusStop() + response.map { + it + } + } catch (e: Exception) { + e.printStackTrace() + emptyList() + } + } + } + + // Busca paradas de ônibus por nome ou endereço + suspend fun getByTermoBusca(nameOrAdress: String): List { + val apiService = Retrofit().apiService + return withContext(Dispatchers.IO) { + try { + val response = apiService.getBusStopByNameOrAddress("Bearer $AUTH_TOKEN", nameOrAdress) + response.map { + it + } + } catch (e: Exception) { + e.printStackTrace() + emptyList() + } + } + } + + + // Busca horários de chegada de ônibus para uma parada específica + suspend fun fetchBusArriveTime(stopCode: String) { + viewModelScope.launch { + val busArriveTimeList = getBusArriveTime(stopCode) + _listOfBus.value = busArriveTimeList.toMutableList() + } + } + + // Obtém os horários de chegada dos ônibus para um código de parada específico + private suspend fun getBusArriveTime(busStopCode: String): List { + return withContext(Dispatchers.IO) { + try { + val response = Retrofit().apiService.getBusByBusStopCode("Bearer $AUTH_TOKEN", busStopCode) + response.busStop.line.flatMap { line -> + line.vehicles.map { vehicle -> + BusLineVehicle(line, vehicle) + } + } + } catch (e: Exception) { + e.printStackTrace() + emptyList() + } + } + } +} diff --git a/MyApplication/app/src/main/res/drawable/bottom_border_rounded.xml b/MyApplication/app/src/main/res/drawable/bottom_border_rounded.xml new file mode 100644 index 0000000..fbcea4a --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/bottom_border_rounded.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/bus.png b/MyApplication/app/src/main/res/drawable/bus.png new file mode 100644 index 0000000..2f2fc69 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/bus.png differ diff --git a/MyApplication/app/src/main/res/drawable/bus2.png b/MyApplication/app/src/main/res/drawable/bus2.png new file mode 100644 index 0000000..9df5c30 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/bus2.png differ diff --git a/MyApplication/app/src/main/res/drawable/bus_stop.png b/MyApplication/app/src/main/res/drawable/bus_stop.png new file mode 100644 index 0000000..96da2e9 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/bus_stop.png differ diff --git a/MyApplication/app/src/main/res/drawable/buss4.png b/MyApplication/app/src/main/res/drawable/buss4.png new file mode 100644 index 0000000..2904951 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/buss4.png differ diff --git a/MyApplication/app/src/main/res/drawable/busspoint.png b/MyApplication/app/src/main/res/drawable/busspoint.png new file mode 100644 index 0000000..109051c Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/busspoint.png differ diff --git a/MyApplication/app/src/main/res/drawable/busspoint2.png b/MyApplication/app/src/main/res/drawable/busspoint2.png new file mode 100644 index 0000000..5cd84f6 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/busspoint2.png differ diff --git a/MyApplication/app/src/main/res/drawable/busstation.png b/MyApplication/app/src/main/res/drawable/busstation.png new file mode 100644 index 0000000..7180d2b Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/busstation.png differ diff --git a/MyApplication/app/src/main/res/drawable/button_background.xml b/MyApplication/app/src/main/res/drawable/button_background.xml new file mode 100644 index 0000000..ac310db --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/button_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/edittext_background.xml b/MyApplication/app/src/main/res/drawable/edittext_background.xml new file mode 100644 index 0000000..a4fa4d5 --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/edittext_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml b/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyApplication/app/src/main/res/drawable/ic_launcher_foreground.xml b/MyApplication/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/icon_buss.png b/MyApplication/app/src/main/res/drawable/icon_buss.png new file mode 100644 index 0000000..ca1945c Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/icon_buss.png differ diff --git a/MyApplication/app/src/main/res/drawable/icon_line_buss.png b/MyApplication/app/src/main/res/drawable/icon_line_buss.png new file mode 100644 index 0000000..ba3247f Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/icon_line_buss.png differ diff --git a/MyApplication/app/src/main/res/drawable/icon_ping_buss.png b/MyApplication/app/src/main/res/drawable/icon_ping_buss.png new file mode 100644 index 0000000..2f2fc69 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/icon_ping_buss.png differ diff --git a/MyApplication/app/src/main/res/drawable/menu_background.xml b/MyApplication/app/src/main/res/drawable/menu_background.xml new file mode 100644 index 0000000..bf24edf --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/menu_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/onibus.gif b/MyApplication/app/src/main/res/drawable/onibus.gif new file mode 100644 index 0000000..90bfa87 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/onibus.gif differ diff --git a/MyApplication/app/src/main/res/drawable/rounded_background.xml b/MyApplication/app/src/main/res/drawable/rounded_background.xml new file mode 100644 index 0000000..f87e454 --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/rounded_background.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/search.png b/MyApplication/app/src/main/res/drawable/search.png new file mode 100644 index 0000000..1a99787 Binary files /dev/null and b/MyApplication/app/src/main/res/drawable/search.png differ diff --git a/MyApplication/app/src/main/res/drawable/strings.xml b/MyApplication/app/src/main/res/drawable/strings.xml new file mode 100644 index 0000000..39a78ba --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/strings.xml @@ -0,0 +1,11 @@ + + + //text menu + Aiko transportes + Paradas + Linhas + Ônibus + Para onde vc quer ir? + Pesquisar + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/text_main_background.xml b/MyApplication/app/src/main/res/drawable/text_main_background.xml new file mode 100644 index 0000000..b5bc2aa --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/text_main_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/drawable/top_rounded_board.xml b/MyApplication/app/src/main/res/drawable/top_rounded_board.xml new file mode 100644 index 0000000..e876bcb --- /dev/null +++ b/MyApplication/app/src/main/res/drawable/top_rounded_board.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/layout/activity_main.xml b/MyApplication/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..188662e --- /dev/null +++ b/MyApplication/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/MyApplication/app/src/main/res/layout/bus_object.xml b/MyApplication/app/src/main/res/layout/bus_object.xml new file mode 100644 index 0000000..cb1002b --- /dev/null +++ b/MyApplication/app/src/main/res/layout/bus_object.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/layout/fragment_line.xml b/MyApplication/app/src/main/res/layout/fragment_line.xml new file mode 100644 index 0000000..55f08a3 --- /dev/null +++ b/MyApplication/app/src/main/res/layout/fragment_line.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyApplication/app/src/main/res/layout/lines_object.xml b/MyApplication/app/src/main/res/layout/lines_object.xml new file mode 100644 index 0000000..d57e76a --- /dev/null +++ b/MyApplication/app/src/main/res/layout/lines_object.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + +