diff --git a/README.md b/README.md index 7651dc89..e6541e6b 100644 --- a/README.md +++ b/README.md @@ -177,3 +177,8 @@ If you find ShowCase useful and would like to support its development, please co paypal + +## Team Members +* Mehmet Eren Tutas +* Doga Celebi +* Gaye Gulmez diff --git a/app/src/main/java/com/wirelessalien/android/moviedb/activity/CastActivity.kt b/app/src/main/java/com/wirelessalien/android/moviedb/activity/CastActivity.kt index 254a079a..fd1448f0 100644 --- a/app/src/main/java/com/wirelessalien/android/moviedb/activity/CastActivity.kt +++ b/app/src/main/java/com/wirelessalien/android/moviedb/activity/CastActivity.kt @@ -663,7 +663,15 @@ class CastActivity : BaseActivity() { castMovieArrayList.add(actorMovies) } } - + castMovieArrayList.sortWith( + compareByDescending { + it.optDouble("vote_average", 0.0) + }.thenByDescending { + it.optDouble("vote_count", 0.0) + }.thenByDescending { + it.optDouble("popularity", 0.0) + } + ) castMovieAdapter = ShowBaseAdapter(context, castMovieArrayList, mShowGenreList!!, preferences.getBoolean(SHOWS_LIST_PREFERENCE, true)) binding.castMovieRecyclerView.adapter = castMovieAdapter } @@ -685,6 +693,15 @@ class CastActivity : BaseActivity() { crewMovieArrayList.add(crewMovies) } } + crewMovieArrayList.sortWith( + compareByDescending { + it.optDouble("vote_average", 0.0) + }.thenByDescending { + it.optDouble("vote_count", 0.0) + }.thenByDescending { + it.optDouble("popularity", 0.0) + } + ) crewMovieAdapter = ShowBaseAdapter(context, crewMovieArrayList, mShowGenreList!!, preferences.getBoolean(SHOWS_LIST_PREFERENCE, true)) binding.crewMovieRecyclerView.adapter = crewMovieAdapter diff --git a/app/src/main/java/com/wirelessalien/android/moviedb/activity/DetailActivity.kt b/app/src/main/java/com/wirelessalien/android/moviedb/activity/DetailActivity.kt index 66b2f001..08502259 100644 --- a/app/src/main/java/com/wirelessalien/android/moviedb/activity/DetailActivity.kt +++ b/app/src/main/java/com/wirelessalien/android/moviedb/activity/DetailActivity.kt @@ -787,25 +787,25 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated } val shareText = """ - *${title}* + 🎬 $title - *Overview:* - $overview + ${if (isMovie) "Movie" else "TV Show"} + ⭐ TMDB: ${binding.rating.text} + IMDb: ${binding.imdbRatingChip.text.toString().replace("IMDb ", "")} + Metacritic: $metacriticRating + Rotten Tomatoes: $rottenTomatoRating - *TMDB Link:* - $tmdbLink + Overview: + $overview - *Ratings:* - - TMDB: ${binding.rating.text} - - IMDb: ${binding.imdbRatingChip.text.toString().replace("IMDb ", "")} - - Metacritic: $metacriticRating - - Rotten Tomatoes: $rottenTomatoRating + My MovieDB Notes: + • My Rating: $userRatingString + • My Review: ${reviewString.ifBlank { getString(R.string.rating_na) }} + • Watched: $timesWatchedString time(s) - - My Review: $reviewString - - I watched: $timesWatchedString times - - My $userRatingString - - """.trimIndent() + Open on TMDB: + $tmdbLink +""".trimIndent() val shareIntent = Intent(Intent.ACTION_SEND) shareIntent.type = "text/plain" @@ -4374,10 +4374,26 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated val reader = JSONObject(response) val similarMovieArray = reader.getJSONArray("results") similarMovieArrayList.clear() + for (i in 0 until similarMovieArray.length()) { val movieData = similarMovieArray.getJSONObject(i) - similarMovieArrayList.add(movieData) + + val isAdult = movieData.optBoolean("adult", false) + val posterPath = movieData.optString("poster_path", "") + + if (!isAdult && posterPath.isNotBlank() && posterPath != "null") { + similarMovieArrayList.add(movieData) + } } + + similarMovieArrayList.sortWith( + compareByDescending { + it.optDouble("vote_average", 0.0) + }.thenByDescending { + it.optDouble("popularity", 0.0) + } + ) + similarMovieAdapter = SimilarMovieBaseAdapter( similarMovieArrayList, applicationContext ) @@ -4387,6 +4403,7 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated je.printStackTrace() } } + hideEmptyRecyclerView(binding.movieRecyclerView, binding.similarMovieTitle) } @@ -4511,12 +4528,42 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated val reviewsObject = movieData.getJSONObject("reviews") val resultsArray = reviewsObject.getJSONArray("results") - // Extract the first three reviews + // Filter and prioritize reviews before showing them on the detail screen + val spoilerKeywords = listOf( + "spoiler", "ending", "final scene", "dies", "death", "killed", "killer", "reveals" + ) + val reviewList = mutableListOf() - for (i in 0 until minOf(3, resultsArray.length())) { - reviewList.add(resultsArray.getJSONObject(i)) + + for (i in 0 until resultsArray.length()) { + val review = resultsArray.getJSONObject(i) + val content = review.optString("content", "").trim() + + if (content.isBlank() || content.length < 50) { + continue + } + + val containsSpoiler = spoilerKeywords.any { keyword -> + content.contains(keyword, ignoreCase = true) + } + + if (containsSpoiler) { + review.put("content", "⚠️ Possible spoiler\n\n$content") + } + + reviewList.add(review) } + reviewList.sortWith( + compareByDescending { + it.optJSONObject("author_details")?.optDouble("rating", 0.0) ?: 0.0 + }.thenByDescending { + it.optString("content", "").length + } + ) + + val limitedReviewList = reviewList.take(3) + // Set up the RecyclerView val reviewAdapter = object : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReviewAdapter.ReviewViewHolder { @@ -4525,10 +4572,10 @@ class DetailActivity : BaseActivity(), ListTmdbBottomSheetFragment.OnListCreated } override fun onBindViewHolder(holder: ReviewAdapter.ReviewViewHolder, position: Int) { - holder.bind(reviewList[position]) + holder.bind(limitedReviewList[position]) } - override fun getItemCount(): Int = reviewList.size + override fun getItemCount(): Int = limitedReviewList.size } binding.recyclerViewReviews.layoutManager = LinearLayoutManager(this) diff --git a/app/src/main/java/com/wirelessalien/android/moviedb/adapter/NowPlayingMovieAdapter.kt b/app/src/main/java/com/wirelessalien/android/moviedb/adapter/NowPlayingMovieAdapter.kt index ca08ce31..70b85bea 100644 --- a/app/src/main/java/com/wirelessalien/android/moviedb/adapter/NowPlayingMovieAdapter.kt +++ b/app/src/main/java/com/wirelessalien/android/moviedb/adapter/NowPlayingMovieAdapter.kt @@ -38,6 +38,7 @@ import org.json.JSONObject import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale +import java.util.Date class NowPlayingMovieAdapter(private val mShowArrayList: ArrayList?) : RecyclerView.Adapter() { @@ -73,16 +74,36 @@ class NowPlayingMovieAdapter(private val mShowArrayList: ArrayList?) if (showData.has(KEY_NAME)) R.drawable.ic_tv_show else R.drawable.ic_movie ) - var dateString = if (showData.has(KEY_DATE_MOVIE)) showData.getString(KEY_DATE_MOVIE) else showData.getString(KEY_DATE_SERIES) + val rawDate = if (showData.has(KEY_DATE_MOVIE)) { + showData.optString(KEY_DATE_MOVIE, "") + } else { + showData.optString(KEY_DATE_SERIES, "") + } + val originalFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + val localFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.getDefault()) + try { - val date = originalFormat.parse(dateString) - val localFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, Locale.getDefault()) - dateString = localFormat.format(date) + val releaseDate = originalFormat.parse(rawDate) + val today = Date() + + if (releaseDate != null) { + val diffInMillis = releaseDate.time - today.time + val diffInDays = diffInMillis / (1000 * 60 * 60 * 24) + + val formattedDate = localFormat.format(releaseDate) + + holder.binding.date.text = when { + diffInDays > 0 -> "Upcoming • $formattedDate" + diffInDays in -30..0 -> "New • $formattedDate" + else -> formattedDate + } + } else { + holder.binding.date.text = rawDate + } } catch (e: ParseException) { - e.printStackTrace() + holder.binding.date.text = rawDate } - holder.binding.date.text = dateString } catch (e: JSONException) { e.printStackTrace() } diff --git a/app/src/main/java/com/wirelessalien/android/moviedb/adapter/ReviewAdapter.kt b/app/src/main/java/com/wirelessalien/android/moviedb/adapter/ReviewAdapter.kt index eb5d9b15..a613425e 100644 --- a/app/src/main/java/com/wirelessalien/android/moviedb/adapter/ReviewAdapter.kt +++ b/app/src/main/java/com/wirelessalien/android/moviedb/adapter/ReviewAdapter.kt @@ -31,6 +31,7 @@ import com.wirelessalien.android.moviedb.R import com.wirelessalien.android.moviedb.databinding.ReviewItemBinding import org.json.JSONObject import java.util.Locale +import android.graphics.Typeface class ReviewAdapter : PagingDataAdapter(ReviewDiffCallback()) { @@ -77,18 +78,26 @@ class ReviewAdapter : } else { binding.root.context.getString(R.string.rating_na) } - // Set content with max lines and click listener + // Set content with spoiler warning, max lines and click listener val content = review.optString("content") - binding.textViewContent.text = content - binding.textViewContent.post { - if (binding.textViewContent.lineCount > 3) { - binding.textViewContent.maxLines = 3 - isContentExpanded = false - } else { - isContentExpanded = true - } + val isSpoiler = content.startsWith("⚠️ Possible spoiler") + + if (isSpoiler) { + val cleanedContent = content + .replace("⚠️ Possible spoiler", "") + .trim() + + binding.textViewContent.text = "⚠️ SPOILER WARNING\n\n$cleanedContent" + binding.textViewContent.setTypeface(null, Typeface.BOLD) + binding.textViewContent.alpha = 0.9f + } else { + binding.textViewContent.text = content + binding.textViewContent.setTypeface(null, Typeface.NORMAL) + binding.textViewContent.alpha = 1.0f } + binding.textViewContent.post { + binding.textViewContent.setOnClickListener { if (binding.textViewContent.lineCount > 3) { isContentExpanded = !isContentExpanded @@ -111,6 +120,17 @@ class ReviewAdapter : return oldItem.getString("id") == newItem.getString("id") } + override fun areContentsTheSame(oldItem: JSONObject, newItem: JSONObject): Boolean { + return oldItem.toString() == newItem.toString() + } + } +} + + class ReviewDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: JSONObject, newItem: JSONObject): Boolean { + return oldItem.optString("id") == newItem.optString("id") + } + override fun areContentsTheSame(oldItem: JSONObject, newItem: JSONObject): Boolean { return oldItem.toString() == newItem.toString() } diff --git a/app/src/main/java/com/wirelessalien/android/moviedb/fragment/HomeFragment.kt b/app/src/main/java/com/wirelessalien/android/moviedb/fragment/HomeFragment.kt index 30a7210b..80f2cd8e 100644 --- a/app/src/main/java/com/wirelessalien/android/moviedb/fragment/HomeFragment.kt +++ b/app/src/main/java/com/wirelessalien/android/moviedb/fragment/HomeFragment.kt @@ -75,6 +75,8 @@ class HomeFragment : BaseFragment() { private lateinit var mTVShowAdapter: NowPlayingMovieAdapter private lateinit var mUpcomingMovieAdapter: NowPlayingMovieAdapter private lateinit var mUpcomingTVAdapter: NowPlayingMovieAdapter + private lateinit var mTopRatedMovieArrayList: ArrayList + private lateinit var mTopRatedMovieAdapter: NowPlayingMovieAdapter private lateinit var binding: FragmentHomeBinding private lateinit var activityBinding: ActivityMainBinding private lateinit var menuProvider: MenuProvider @@ -101,6 +103,7 @@ class HomeFragment : BaseFragment() { fetchTrendingList() fetchUpcomingMovies() fetchUpcomingTVShows() + } } } @@ -117,6 +120,7 @@ class HomeFragment : BaseFragment() { showTrendingList() showUpcomingMovieList() showUpcomingTVShowList() + // showTopRatedMovieList() activityBinding.fab2.visibility = View.GONE activityBinding.fab.visibility = View.GONE activityBinding.searchView.setupWithSearchBar(binding.searchbar) @@ -227,11 +231,13 @@ class HomeFragment : BaseFragment() { mUpcomingTVAdapter = NowPlayingMovieAdapter(mUpcomingTVShowArrayList) mUpcomingMovieArrayList = ArrayList() mUpcomingMovieAdapter = NowPlayingMovieAdapter(mUpcomingMovieArrayList) + mTopRatedMovieArrayList = ArrayList() + mTopRatedMovieAdapter = NowPlayingMovieAdapter(mTopRatedMovieArrayList) (requireActivity() as BaseActivity).checkNetwork() } private fun setupRecyclerView( - recyclerView: RecyclerView?, + recyclerView: RecyclerView, layoutManager: LinearLayoutManager, adapter: RecyclerView.Adapter<*>? ) { @@ -258,7 +264,10 @@ class HomeFragment : BaseFragment() { val layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) setupRecyclerView(binding.upcomingMovieRecyclerView, layoutManager, mUpcomingMovieAdapter) } - + private fun showTopRatedMovieList() { + val layoutManager = LinearLayoutManager(activity, LinearLayoutManager.HORIZONTAL, false) + setupRecyclerView(binding.upcomingMovieRecyclerView, layoutManager, mTopRatedMovieAdapter) + } private suspend fun fetchUpcomingTVShows() { withContext(Dispatchers.Main) { binding.shimmerFrameLayout5.visibility = View.VISIBLE @@ -272,7 +281,7 @@ class HomeFragment : BaseFragment() { val response = fetchData(url) withContext(Dispatchers.Main) { if (isAdded && !response.isNullOrEmpty()) { - handleUpcomingTVResponse(response) + handleMovieResponse(response) } else { binding.shimmerFrameLayout5.visibility = View.GONE binding.shimmerFrameLayout5.stopShimmer() @@ -323,7 +332,7 @@ class HomeFragment : BaseFragment() { val response = fetchData(url) withContext(Dispatchers.Main) { if (isAdded && !response.isNullOrEmpty()) { - handleMovieResponse(response) + //SDFKLNHGJKSHKDJFGHSJKLGHDFJKFDGHKJFDHJKFGDHJGIDFK } else { binding.shimmerFrameLayout2.visibility = View.GONE binding.shimmerFrameLayout2.stopShimmer() @@ -374,10 +383,12 @@ class HomeFragment : BaseFragment() { val reader = JSONObject(response) val arrayData = reader.getJSONArray("results") mHomeShowArrayList.clear() + for (i in 0 until arrayData.length()) { val websiteData = arrayData.getJSONObject(i) mHomeShowArrayList.add(websiteData) } + binding.nowPlayingRecyclerView.adapter = mHomeShowAdapter mShowListLoaded = true binding.shimmerFrameLayout2.visibility = View.GONE