diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.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/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..ae388c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..5ac1239 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,67 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("kotlin-kapt") + id ("kotlin-parcelize") // @parcelize annotation + id("androidx.navigation.safeargs.kotlin") // for safeArgs +} + +android { + namespace = "com.project.movievault" + compileSdk = 34 + + defaultConfig { + applicationId = "com.project.movievault" + minSdk = 24 + targetSdk = 33 + 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 { + dataBinding = true + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + + implementation("androidx.navigation:navigation-fragment-ktx:2.6.0") + implementation("androidx.navigation:navigation-ui-ktx:2.6.0") + + // Moshi + implementation("com.squareup.moshi:moshi-kotlin:1.13.0") + + // Retrofit with Moshi Converter + implementation("com.squareup.retrofit2:converter-moshi:2.9.0") + + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0") + + // Coil + implementation("io.coil-kt:coil:2.2.2") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/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/app/src/androidTest/java/com/project/movievault/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/project/movievault/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..e9c6964 --- /dev/null +++ b/app/src/androidTest/java/com/project/movievault/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.project.movievault + +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.project.movievault", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..471b76e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/MainActivity.kt b/app/src/main/java/com/project/movievault/MainActivity.kt new file mode 100644 index 0000000..1f20d2d --- /dev/null +++ b/app/src/main/java/com/project/movievault/MainActivity.kt @@ -0,0 +1,11 @@ +package com.project.movievault + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/adapters/BindingAdapters.kt b/app/src/main/java/com/project/movievault/adapters/BindingAdapters.kt new file mode 100644 index 0000000..7b11f76 --- /dev/null +++ b/app/src/main/java/com/project/movievault/adapters/BindingAdapters.kt @@ -0,0 +1,41 @@ +package com.project.movievault.adapters + +import android.view.View +import android.widget.ImageView +import androidx.core.net.toUri +import androidx.databinding.BindingAdapter +import coil.load +import com.project.movievault.R +import com.project.movievault.viewmodel.MovieApiStatus + +@BindingAdapter("imageUrl") +fun bindImage(imgView: ImageView, imgUrl: String?) { + imgUrl?.let { + val imgUri = imgUrl.toUri().buildUpon().scheme("https").build() + imgView.load(imgUri) { + placeholder(R.drawable.loading_animation) + error(R.drawable.ic_broken_image) + } + } +} + +@BindingAdapter("moviesApiStatus") +fun bindStatus(statusImageView: ImageView, status: MovieApiStatus?) { + when(status) { + MovieApiStatus.LOADING -> { + statusImageView.visibility = View.VISIBLE + statusImageView.setImageResource(R.drawable.loading_animation) + } + MovieApiStatus.ERROR -> { + statusImageView.visibility = View.VISIBLE + statusImageView.setImageResource(R.drawable.ic_connection_error) + } + MovieApiStatus.DONE -> { + statusImageView.visibility = View.GONE + } + + else -> { + statusImageView.visibility = View.GONE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/adapters/ShowsAdapter.kt b/app/src/main/java/com/project/movievault/adapters/ShowsAdapter.kt new file mode 100644 index 0000000..2ea550d --- /dev/null +++ b/app/src/main/java/com/project/movievault/adapters/ShowsAdapter.kt @@ -0,0 +1,38 @@ +package com.project.movievault.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.project.movievault.databinding.MovieItemBinding +import com.project.movievault.model.ShowDetails +import com.project.movievault.model.ShowResponse + +class ShowsAdapter(private val myDataset: List, private val listener: MyShowsItemClickListener) : RecyclerView.Adapter() { + class ShowsViewHolder(private val binding: MovieItemBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind(showResponse: ShowResponse, listener: MyShowsItemClickListener) { + binding.titleTV.text = showResponse.show.name + binding.image = showResponse.show.image + binding.root.setOnClickListener { + listener.onShowItemClicked(showResponse.show) + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShowsViewHolder { + return ShowsViewHolder(MovieItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + } + + override fun getItemCount() = myDataset.size + + override fun onBindViewHolder(holder: ShowsViewHolder, position: Int) { +// val data = myDataset[position] +// val urlToImage = data.urlToImage +// if(urlToImage!=null) + holder.bind(myDataset[position], listener) +// else Log.d("adfasl", "Url of image was null here") + } +} + +interface MyShowsItemClickListener { + fun onShowItemClicked(showDetails: ShowDetails) +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/fragments/DetailsFragment.kt b/app/src/main/java/com/project/movievault/fragments/DetailsFragment.kt new file mode 100644 index 0000000..e4d5967 --- /dev/null +++ b/app/src/main/java/com/project/movievault/fragments/DetailsFragment.kt @@ -0,0 +1,40 @@ +package com.project.movievault.fragments + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.navArgs +import com.project.movievault.databinding.FragmentDetailsBinding + +class DetailsFragment : Fragment() { + private var _binding: FragmentDetailsBinding? = null + private val binding get() = _binding!! + private val args: DetailsFragmentArgs by navArgs() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + // Inflate the layout for this fragment + _binding = FragmentDetailsBinding.inflate(inflater, container, false) + + val show = args.show + binding.showDetails = show + binding.visitSiteBtn.setOnClickListener { + val uri: Uri = Uri.parse(show.officialSite) + val intent = Intent(Intent.ACTION_VIEW, uri) + startActivity(intent) + } + + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/fragments/MovieListFragment.kt b/app/src/main/java/com/project/movievault/fragments/MovieListFragment.kt new file mode 100644 index 0000000..6574b23 --- /dev/null +++ b/app/src/main/java/com/project/movievault/fragments/MovieListFragment.kt @@ -0,0 +1,53 @@ +package com.project.movievault.fragments + +import android.os.Bundle +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.project.movievault.adapters.MyShowsItemClickListener +import com.project.movievault.adapters.ShowsAdapter +import com.project.movievault.databinding.FragmentMovieListBinding +import com.project.movievault.model.ShowDetails +import com.project.movievault.viewmodel.MovieViewModel + +class MovieListFragment : Fragment(), MyShowsItemClickListener { + + private val viewModel: MovieViewModel by viewModels() + private var _binding: FragmentMovieListBinding? = null + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + _binding = FragmentMovieListBinding.inflate(inflater, container, false) + binding.lifecycleOwner = this + binding.viewModel = viewModel + + viewModel.shows.observe(viewLifecycleOwner) { data -> + Log.d("adfasl", "Data Changed $data") + + val showsAdp = ShowsAdapter(data, this) + binding.showsRV.adapter = showsAdp + } + + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + override fun onShowItemClicked(showDetails: ShowDetails) { + val action = MovieListFragmentDirections.actionMovieListFragmentToDetailsFragment(showDetails) + findNavController().navigate(action) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/model/Movie.kt b/app/src/main/java/com/project/movievault/model/Movie.kt new file mode 100644 index 0000000..b751222 --- /dev/null +++ b/app/src/main/java/com/project/movievault/model/Movie.kt @@ -0,0 +1,34 @@ +package com.project.movievault.model + +import android.os.Parcelable +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.squareup.moshi.Json +import kotlinx.parcelize.Parcelize + +@JsonIgnoreProperties(ignoreUnknown = true) +data class ShowResponse( + val show: ShowDetails +) + +@JsonIgnoreProperties(ignoreUnknown = true) +@Parcelize +data class ShowDetails( + val name: String?, + val genres: List?, + val premiered: String?, + val officialSite: String?, + val rating: Rating, + val image: Image?, + val summary: String?, +) : Parcelable + +@Parcelize +data class Rating( + val average: Double? +) : Parcelable + +@JsonIgnoreProperties(ignoreUnknown = true) +@Parcelize +data class Image( + val medium: String +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/network/MovieApiService.kt b/app/src/main/java/com/project/movievault/network/MovieApiService.kt new file mode 100644 index 0000000..a23743c --- /dev/null +++ b/app/src/main/java/com/project/movievault/network/MovieApiService.kt @@ -0,0 +1,29 @@ +package com.project.movievault.network + +import com.project.movievault.model.ShowResponse +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.http.GET +import retrofit2.http.Query + +private const val BASE_URL = "https://api.tvmaze.com/" + +private val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + +private val retrofit = Retrofit.Builder() + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .baseUrl(BASE_URL) + .build() + +interface MovieApiService { + @GET("search/shows?q=all") + suspend fun getShowResponses(): List +} + +object MoviesApi { + val retrofitService: MovieApiService by lazy { retrofit.create(MovieApiService::class.java) } +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/utils/DataBindingUtils.kt b/app/src/main/java/com/project/movievault/utils/DataBindingUtils.kt new file mode 100644 index 0000000..1e8bbd5 --- /dev/null +++ b/app/src/main/java/com/project/movievault/utils/DataBindingUtils.kt @@ -0,0 +1,32 @@ +package com.project.movievault.utils + +import android.util.Log + +object DataBindingUtils { + + @JvmStatic + fun removeTags(summary: String?): String { + val list = summary?.split("

","

","","") + var returnString = "" + if (list != null) { + for(s: String in list) { + + returnString += s + } + } + + return returnString + } + + @JvmStatic + fun listToStr(genres: List?): String { + var returnString = "" + if (genres != null) { + for(s: String in genres) { + returnString += ("$s ") + } + } + + return "Genres: $returnString" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/project/movievault/viewmodel/MovieViewModel.kt b/app/src/main/java/com/project/movievault/viewmodel/MovieViewModel.kt new file mode 100644 index 0000000..a4488cd --- /dev/null +++ b/app/src/main/java/com/project/movievault/viewmodel/MovieViewModel.kt @@ -0,0 +1,41 @@ +package com.project.movievault.viewmodel + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.project.movievault.model.ShowResponse +import com.project.movievault.network.MoviesApi +import kotlinx.coroutines.launch + +enum class MovieApiStatus { + LOADING, ERROR, DONE +} + +class MovieViewModel : ViewModel() { + + private val _status = MutableLiveData() + val status: LiveData get() = _status + + private val _shows = MutableLiveData>() + val shows: LiveData> get() = _shows + + init { + getShowResponses() + } + + private fun getShowResponses() { + viewModelScope.launch { + _status.value = MovieApiStatus.LOADING + try { + _shows.value = MoviesApi.retrofitService.getShowResponses() + _status.value = MovieApiStatus.DONE + } catch (e: Exception) { + Log.d("abcd",e.toString()) + _status.value = MovieApiStatus.ERROR + _shows.value = listOf() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_broken_image.xml b/app/src/main/res/drawable/ic_broken_image.xml new file mode 100644 index 0000000..c8d6dd3 --- /dev/null +++ b/app/src/main/res/drawable/ic_broken_image.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_connection_error.xml b/app/src/main/res/drawable/ic_connection_error.xml new file mode 100644 index 0000000..ec52aa8 --- /dev/null +++ b/app/src/main/res/drawable/ic_connection_error.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.png b/app/src/main/res/drawable/ic_launcher_background.png new file mode 100644 index 0000000..cbfb94c Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_background.png differ diff --git a/app/src/main/res/drawable/ic_launcher_foreground.png b/app/src/main/res/drawable/ic_launcher_foreground.png new file mode 100644 index 0000000..1eb6fa6 Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_foreground.png differ diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.png b/app/src/main/res/drawable/ic_launcher_monochrome.png new file mode 100644 index 0000000..1eb6fa6 Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/drawable/ic_launcher_round_background.png b/app/src/main/res/drawable/ic_launcher_round_background.png new file mode 100644 index 0000000..cbfb94c Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_round_background.png differ diff --git a/app/src/main/res/drawable/ic_launcher_round_foreground.png b/app/src/main/res/drawable/ic_launcher_round_foreground.png new file mode 100644 index 0000000..1eb6fa6 Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_round_foreground.png differ diff --git a/app/src/main/res/drawable/ic_launcher_round_monochrome.png b/app/src/main/res/drawable/ic_launcher_round_monochrome.png new file mode 100644 index 0000000..1eb6fa6 Binary files /dev/null and b/app/src/main/res/drawable/ic_launcher_round_monochrome.png differ diff --git a/app/src/main/res/drawable/loading_animation.xml b/app/src/main/res/drawable/loading_animation.xml new file mode 100644 index 0000000..75ba385 --- /dev/null +++ b/app/src/main/res/drawable/loading_animation.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/main/res/drawable/loading_img.xml b/app/src/main/res/drawable/loading_img.xml new file mode 100644 index 0000000..6b3e5b7 --- /dev/null +++ b/app/src/main/res/drawable/loading_img.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..c247417 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml new file mode 100644 index 0000000..ef8b562 --- /dev/null +++ b/app/src/main/res/layout/fragment_details.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +