Repository: Sovan22/Tokeii Branch: master Commit: 754b8503003b Files: 185 Total size: 528.8 KB Directory structure: gitextract_qqda0tz5/ ├── .gitignore ├── .idea/ │ ├── .gitignore │ ├── compiler.xml │ ├── gradle.xml │ ├── kotlinc.xml │ ├── misc.xml │ └── vcs.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── demomiru/ │ │ └── tokeiv2/ │ │ └── ExampleInstrumentedTest.kt │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── demomiru/ │ │ │ └── tokeiv2/ │ │ │ ├── EpisodeAdapter.kt │ │ │ ├── EpisodeAdapter2.kt │ │ │ ├── MainActivity.kt │ │ │ ├── Movie.kt │ │ │ ├── MovieAdapter.kt │ │ │ ├── MoviePlayActivity.kt │ │ │ ├── MovieService.kt │ │ │ ├── MoviesFragment.kt │ │ │ ├── SearchFragment.kt │ │ │ ├── TMDBService.kt │ │ │ ├── TVShowAdapter.kt │ │ │ ├── TVShowCardAdapter.kt │ │ │ ├── TVShowDetails.kt │ │ │ ├── TVShowFragment.kt │ │ │ ├── TVshow.kt │ │ │ ├── VideoPlayActivity.kt │ │ │ ├── anime/ │ │ │ │ ├── AnimeAdapter.kt │ │ │ │ ├── AnimeDetailsFragment.kt │ │ │ │ ├── AnimeFragment.kt │ │ │ │ └── AnimeInfo.kt │ │ │ ├── extractors/ │ │ │ │ ├── CorrectTitleSelection.kt │ │ │ │ ├── PrMovies.kt │ │ │ │ └── Vidplay.kt │ │ │ ├── history/ │ │ │ │ ├── QueryRepository.kt │ │ │ │ ├── SearchApp.kt │ │ │ │ ├── SearchDatabase.kt │ │ │ │ ├── SearchHistory.kt │ │ │ │ ├── SearchHistoryAdapter.kt │ │ │ │ ├── SearchHistoryAdapter2.kt │ │ │ │ └── SearchHistoryDao.kt │ │ │ ├── subtitles/ │ │ │ │ ├── SubTrackAdapter.kt │ │ │ │ └── Subtitles.kt │ │ │ ├── tracks/ │ │ │ │ └── TrackAdapter.kt │ │ │ ├── utils/ │ │ │ │ ├── DudeFilmsUtils.kt │ │ │ │ ├── Extractor.kt │ │ │ │ ├── GoMovies.kt │ │ │ │ ├── GogoAnime.kt │ │ │ │ ├── OpenSubtitle.kt │ │ │ │ ├── SmashyStream.kt │ │ │ │ ├── SuperstreamUtils.kt │ │ │ │ ├── VidSrc.kt │ │ │ │ ├── VidSrcUtils.kt │ │ │ │ ├── ViewModelsTokei.kt │ │ │ │ ├── colors.txt │ │ │ │ └── retrofitBuilder.kt │ │ │ └── watching/ │ │ │ ├── ContinueWatching.kt │ │ │ ├── ContinueWatchingAdapter.kt │ │ │ ├── ContinueWatchingDao.kt │ │ │ ├── ContinueWatchingDatabase.kt │ │ │ └── ContinueWatchingRepository.kt │ │ └── res/ │ │ ├── anim/ │ │ │ ├── enter_anim.xml │ │ │ ├── enter_from_bottom.xml │ │ │ ├── exit_anim.xml │ │ │ ├── exit_to_top.xml │ │ │ ├── go_left.xml │ │ │ ├── go_right.xml │ │ │ ├── layout_animation.xml │ │ │ ├── nav_enter_anim.xml │ │ │ ├── nav_exit_anim.xml │ │ │ ├── nav_pop_enter.xml │ │ │ ├── nav_pop_exit.xml │ │ │ ├── pop_enter.xml │ │ │ ├── pop_exit.xml │ │ │ ├── rotate_around_center_point.xml │ │ │ ├── rotate_left.xml │ │ │ ├── rotate_right.xml │ │ │ ├── slide_from_uo.xml │ │ │ ├── slide_in.xml │ │ │ ├── slide_in_right.xml │ │ │ └── slide_out.xml │ │ ├── drawable/ │ │ │ ├── anime_bottom_nav.xml │ │ │ ├── background_search_history.xml │ │ │ ├── background_text.xml │ │ │ ├── baseline_cancel_24.xml │ │ │ ├── baseline_double_arrow_24.xml │ │ │ ├── baseline_history_24.xml │ │ │ ├── baseline_keyboard_arrow_down_24.xml │ │ │ ├── baseline_keyboard_arrow_up_24.xml │ │ │ ├── baseline_lock_open_24.xml │ │ │ ├── baseline_play_arrow_24.xml │ │ │ ├── baseline_play_circle_24.xml │ │ │ ├── baseline_search_24.xml │ │ │ ├── baseline_zoom_out_map_24.xml │ │ │ ├── custom_thumb.xml │ │ │ ├── delete.xml │ │ │ ├── down_arrow.xml │ │ │ ├── edit.xml │ │ │ ├── fill_screen.xml │ │ │ ├── fit_screen.xml │ │ │ ├── focus.xml │ │ │ ├── focus_search.xml │ │ │ ├── gradient_fill.xml │ │ │ ├── gradient_fill_bottom.xml │ │ │ ├── gradient_fill_text.xml │ │ │ ├── ic_baseline_keyboard_backspace_24.xml │ │ │ ├── ic_forward.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_menu.xml │ │ │ ├── ic_rewind.xml │ │ │ ├── ic_rotation.xml │ │ │ ├── icon_play.xml │ │ │ ├── image_view_round.xml │ │ │ ├── more.xml │ │ │ ├── netflix_audio_subtitles.xml │ │ │ ├── netflix_brightness_four.xml │ │ │ ├── netflix_brightness_one.xml │ │ │ ├── netflix_brightness_three.xml │ │ │ ├── netflix_brightness_two.xml │ │ │ ├── netflix_lock_black.xml │ │ │ ├── netflix_pause_button.xml │ │ │ ├── netflix_speed.xml │ │ │ ├── netflix_unlock.xml │ │ │ ├── next_episode.xml │ │ │ ├── play_ripple.xml │ │ │ ├── player_controller_bg.xml │ │ │ ├── properties.xml │ │ │ ├── quality_24.xml │ │ │ ├── radio_group_tab_bg.xml │ │ │ ├── rectangle_box.xml │ │ │ ├── rectangle_box_slim.xml │ │ │ ├── selected_quality.xml │ │ │ ├── selected_subtitle.xml │ │ │ ├── share.xml │ │ │ ├── share_link.xml │ │ │ ├── tab_selector.xml │ │ │ ├── tab_text_color_selector.xml │ │ │ ├── volume_up_24.xml │ │ │ └── white_round.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── font/ │ │ │ ├── exo_2.xml │ │ │ └── exo_2_thin.xml │ │ ├── layout/ │ │ │ ├── activity_main.xml │ │ │ ├── activity_movie_play.xml │ │ │ ├── activity_video_play.xml │ │ │ ├── card_view.xml │ │ │ ├── custom_video_player2.xml │ │ │ ├── dropdown_menu.xml │ │ │ ├── episode_expanded_view.xml │ │ │ ├── episode_item_viem.xml │ │ │ ├── fragment_anime.xml │ │ │ ├── fragment_anime_details.xml │ │ │ ├── fragment_correct_title_selection.xml │ │ │ ├── fragment_movies.xml │ │ │ ├── fragment_search.xml │ │ │ ├── fragment_tv_show.xml │ │ │ ├── fragment_tv_show_details.xml │ │ │ ├── item_view.xml │ │ │ ├── search_item.xml │ │ │ ├── sub_view.xml │ │ │ ├── test.xml │ │ │ └── track_item.xml │ │ ├── menu/ │ │ │ └── bottom_nav.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── navigation/ │ │ │ └── nav_main.xml │ │ ├── transition/ │ │ │ └── container_transform.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── font_certs.xml │ │ │ ├── preloaded_fonts.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ ├── values-night/ │ │ │ └── themes.xml │ │ └── xml/ │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test/ │ └── java/ │ └── com/ │ └── demomiru/ │ └── tokeiv2/ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.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 ================================================ FILE: .idea/.gitignore ================================================ # Default ignored files /shelf/ /workspace.xml ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/gradle.xml ================================================ ================================================ FILE: .idea/kotlinc.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ # Tokei Image Description Movie and TV Streaming App ## Download the app here: # [tokei-release_v1.1.4](https://github.com/Sovan22/Tokeii/releases/download/v1.1.4-tokei/tokei-v1.1.4.apk) Compatibility : Android 7+ ## Features: + **AdFree** + No tracking/analytics + Lesser Loading Time + Good sources for Indian OTT Content ## App Demo https://github.com/Sovan22/Tokeii/assets/12212201/b417be95-d728-4af8-9809-b2fcdfde1c5f Movies Search Player ## DISCLAIMER + Tokei only scrapes links from various websites and makes it easier for users to find tvShows and movies. + Tokei doesn't host any of the contents found inside Tokei. Any and all images and movie/tv information found in the app are taken from various public APIs (TMDb). + Furthermore, all of the movie/tv links found in Tokei are taken from various 3rd party hosting websites. + Tokei or it's owners aren't liable for any misuse of any of the contents found inside or outside of the app and cannot be held accountable for the distribution of any of the contents found inside the app. + By using Tokei, you comply to the fact that the developer of the app is not responsible for any of the contents found in the app; nonetheless they may or may not be from their legitimate sources. + If the internet infringement issues are involved, please contact the source website. The developer does not assume any legal responsibility. ================================================ FILE: app/.gitignore ================================================ /build ================================================ FILE: app/build.gradle ================================================ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id("androidx.navigation.safeargs.kotlin") id("com.google.devtools.ksp") } def localProperties = new Properties() localProperties.load(new FileInputStream(rootProject.file("local.properties"))) android { namespace 'com.demomiru.tokeiv2' compileSdk 34 packagingOptions { pickFirst "META-INF/DEPENDENCIES" exclude 'mozilla/public-suffix-list.txt' } defaultConfig { applicationId "com.demomiru.tokeiv2" minSdk 26 targetSdk 34 versionCode 1 versionName "1.1.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "OPEN_SUBTITLE_API_KEY", "\"${localProperties["OPEN_SUBTITLE_API_KEY"]}\"") buildConfigField("String", "TMDB_API_KEY", "\"${localProperties["TMDB_API_KEY"]}\"") buildConfigField("String", "TMDB_TOKEN", "\"${localProperties["TMDB_TOKEN"]}\"") buildConfigField("String", "SUPERSTREAM_API1", "\"${localProperties["SUPERSTREAM_API1"]}\"") buildConfigField("String", "SUPERSTREAM_API2", "\"${localProperties["SUPERSTREAM_API2"]}\"") buildConfigField("String", "PROXY_URL", "\"${localProperties["PROXY_URL"]}\"") buildConfigField("String", "MAL_API", "\"${localProperties["MAL_API"]}\"") } buildTypes { release { minifyEnabled 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 buildConfig true } } dependencies { def paging_version = "3.2.1" implementation "androidx.paging:paging-runtime:$paging_version" implementation 'com.github.ismaeldivita:chip-navigation-bar:1.4.0' implementation 'androidx.core:core-ktx:1.9.0' implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" implementation 'androidx.cardview:cardview:1.0.0' implementation 'at.blogc:expandabletextview:1.0.5' implementation "androidx.navigation:navigation-fragment-ktx:2.7.1" implementation "androidx.navigation:navigation-ui-ktx:2.7.1" implementation 'com.google.firebase:firebase-firestore-ktx:24.1.0' ksp("androidx.room:room-compiler:2.5.0") implementation "androidx.activity:activity-ktx:1.7.2" implementation 'androidx.appcompat:appcompat:1.6.1' // implementation("it.skrape:skrapeit:1.2.2") // implementation 'org.jsoup:jsoup:1.14.3' // implementation 'org.danilopianini:khttp:1.3.1' implementation 'com.github.Blatzar:NiceHttp:0.4.4' implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2' implementation("com.squareup.okhttp3:okhttp:4.10.0") implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2" implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation("io.coil-kt:coil:2.4.0") // Room components implementation "androidx.room:room-runtime:2.5.0" // annotationProcessor "androidx.room:room-compiler:2.5.2" implementation "androidx.room:room-ktx:2.5.0" //ExoPlayer implementation "androidx.media3:media3-exoplayer:1.1.1" implementation "androidx.media3:media3-exoplayer-hls:1.1.1" implementation "androidx.media3:media3-exoplayer-dash:1.1.1" implementation "androidx.media3:media3-ui:1.1.1" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' } ================================================ FILE: app/proguard-rules.pro ================================================ # 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 ================================================ FILE: app/src/androidTest/java/com/demomiru/tokeiv2/ExampleInstrumentedTest.kt ================================================ package com.demomiru.tokeiv2 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.demomiru.tokeiv2", appContext.packageName) } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/EpisodeAdapter.kt ================================================ package com.demomiru.tokeiv2 import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView class EpisodeAdapter(private val episodeNumber : List, private val clickHandler : (Int) -> Unit ) : RecyclerView.Adapter() { class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ val episodeText : TextView = itemView.findViewById(R.id.episode_no_text) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.episode_item_viem,parent,false) return ViewHolder(view) } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val episodeN = episodeNumber[position] holder.episodeText.text = "Episode $episodeN" holder.itemView.setOnClickListener { clickHandler(episodeN) } } override fun getItemCount(): Int { return episodeNumber.size } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/EpisodeAdapter2.kt ================================================ package com.demomiru.tokeiv2 import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import androidx.cardview.widget.CardView import androidx.recyclerview.widget.RecyclerView import at.blogc.android.views.ExpandableTextView import coil.load //class MyDiffUtilCallback : DiffUtil.ItemCallback() { // override fun areItemsTheSame(oldItem: MyItem, newItem: MyItem): Boolean { // // Return true if items are the same. // return oldItem.id == newItem.id // } // // override fun areContentsTheSame(oldItem: MyItem, newItem: MyItem): Boolean { // // Return true if contents are the same. // return oldItem == newItem // } //} class EpisodeAdapter2(private val episodes : List, private val clickHandler : (Episode) -> Unit ) : RecyclerView.Adapter() { class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ val episodeText : TextView = itemView.findViewById(R.id.episode_no_text) val episodeImg : ImageView = itemView.findViewById(R.id.episode_img) val episodeOverview : ExpandableTextView = itemView.findViewById(R.id.episode_overview_text) val fl : FrameLayout = itemView.findViewById(R.id.expanded_episode_fl) val epCard: CardView = itemView.findViewById(R.id.episode_card) val expandText : ImageView = itemView.findViewById(R.id.expand_text) val expandableTextView : ExpandableTextView = itemView.findViewById(R.id.episode_overview_text) val episodeNumber : TextView = itemView.findViewById(R.id.episode_number) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.test,parent,false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val episode = episodes[position] holder.episodeText.text = episode.name holder.episodeNumber.text = episode.episode_number holder.episodeOverview.text = episode.overview holder.episodeImg.load("https://image.tmdb.org/t/p/w500${episode.still_path}") holder.fl.setOnClickListener { clickHandler(episode) } holder.epCard.setOnClickListener { holder.fl.performClick() // clickHandler(episode) } val etv = holder.expandableTextView etv.setAnimationDuration(750L) holder.expandText.setOnClickListener { if(etv.isExpanded){ etv.collapse() holder.expandText.load(R.drawable.baseline_keyboard_arrow_down_24) }else{ etv.expand() holder.expandText.load(R.drawable.baseline_keyboard_arrow_up_24) } } } override fun getItemCount(): Int { return episodes.size } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/MainActivity.kt ================================================ package com.demomiru.tokeiv2 import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.KeyEvent import android.view.View import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.widget.NestedScrollView import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.NavOptions import androidx.navigation.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.demomiru.tokeiv2.utils.ContinueWatchingViewModel2 import com.demomiru.tokeiv2.utils.ContinueWatchingViewModelFactory2 import com.demomiru.tokeiv2.utils.addRecyclerAnimation import com.demomiru.tokeiv2.utils.passData import com.demomiru.tokeiv2.watching.ContinueWatching import com.demomiru.tokeiv2.watching.ContinueWatchingAdapter import com.demomiru.tokeiv2.watching.ContinueWatchingDatabase import com.demomiru.tokeiv2.watching.ContinueWatchingRepository import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.common.primitives.UnsignedBytes.toInt import com.google.gson.Gson import com.lagradost.nicehttp.Requests import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request import org.json.JSONArray import org.jsoup.Jsoup @Suppress("DEPRECATION") class MainActivity : AppCompatActivity() { private val version = 114 private val gson = Gson() private lateinit var watchHistoryRc : RecyclerView private val database by lazy { ContinueWatchingDatabase.getInstance(this) } private val watchHistoryDao by lazy { database.watchDao() } private val app = Requests() private lateinit var viewModelFactory: ContinueWatchingViewModelFactory2 private val viewModel: ContinueWatchingViewModel2 by viewModels( factoryProducer = { viewModelFactory } ) private var currentFragment = MutableLiveData(R.id.moviesFragment) private lateinit var continueText: TextView private lateinit var bottomNavigationView : BottomNavigationView private var nestedScrollView : NestedScrollView? = null private lateinit var adapter: ContinueWatchingAdapter private lateinit var continueWatchingRepository: ContinueWatchingRepository override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModelFactory = ContinueWatchingViewModelFactory2(watchHistoryDao) val options = NavOptions.Builder() .setEnterAnim(R.anim.enter_from_bottom) .setExitAnim(R.anim.exit_to_top) .build() nestedScrollView = findViewById(R.id.nestedScrollView) val navController = findNavController(R.id.nav_host_fragment) bottomNavigationView = findViewById(R.id.bottom_nav_bar) continueWatchingRepository = ContinueWatchingRepository(watchHistoryDao) watchHistoryRc = findViewById(R.id.watch_history_rc) watchHistoryRc.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false) continueText = findViewById(R.id.continue_watching_text) adapter = ContinueWatchingAdapter{it,delete-> if(delete){ lifecycleScope.launch (Dispatchers.IO) { continueWatchingRepository.delete(it) // continueWatchingRepository.loadData() } } else { startActivity(passData(it, this)) } } watchHistoryRc.adapter = adapter bottomNavigationView.setOnNavigationItemSelectedListener { item -> when (item.itemId) { R.id.moviesFragment -> { navController.navigate(R.id.moviesFragment, null, options) if (viewModel.allWatchHistory.value?.size !=0){ watchHistoryRc.visibility = View.VISIBLE continueText.visibility = View.VISIBLE currentFragment.value = R.id.moviesFragment } } R.id.searchFragment -> { navController.navigate(R.id.searchFragment, null, options) watchHistoryRc.visibility = View.GONE continueText.visibility = View.GONE currentFragment.value = R.id.searchFragment } R.id.TVShowFragment -> { navController.navigate(R.id.TVShowFragment, null, options) if (viewModel.allWatchHistory.value?.size !=0){ watchHistoryRc.visibility = View.VISIBLE continueText.visibility = View.VISIBLE currentFragment.value = R.id.TVShowFragment } } R.id.animeFragment ->{ navController.navigate(R.id.animeFragment,null,options) if (viewModel.allWatchHistory.value?.size !=0){ watchHistoryRc.visibility = View.VISIBLE continueText.visibility = View.VISIBLE currentFragment.value = R.id.animeFragment } } } true } bottomNavigationView.setOnNavigationItemReselectedListener { return@setOnNavigationItemReselectedListener } // bottomNavigationView.selectedItemId = R.id.animeFragment lifecycleScope.launch (Dispatchers.IO){ // val update = app.get("https://github.com/Sovan22/Tokeii/").document.select("article.markdown-body.entry-content.container-lg .anchor")[2].attr("href").substringAfter("v").toInt() val client = OkHttpClient() val request = Request.Builder() .url("https://api.github.com/repos/Sovan22/Tokeii/releases").build() val response = client.newCall(request).execute() if (response.isSuccessful) { val releases = JSONArray(response.body.string()) val update = gson.fromJson(releases.getJSONObject(0).toString(),Release::class.java).tag_name.replace(".","").replace("v","").replace("-tokei","").toInt() // [2].attr("href").substringAfter("v").toInt() println(update) if (version < update) withContext(Dispatchers.Main) { showDialog() } } } } data class Release(val tag_name:String) fun triggerSearchKeyPress() { val enterKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER) dispatchKeyEvent(enterKeyEvent) } @SuppressLint("SetTextI18n") override fun onResume() { // viewModel.currentFragment.observe(this){ // if(it == R.id.searchFragment ){ // watchHistoryRc.visibility = View.GONE // continueText.visibility = View.GONE // } // else{ // watchHistoryRc.visibility = View.VISIBLE // continueText.visibility = View.VISIBLE // } // } // viewModel.showContinue.observe(this){ // if(!it){ // watchHistoryRc.visibility = View.GONE // continueText.visibility = View.GONE // } // else{ // watchHistoryRc.visibility = View.VISIBLE // continueText.visibility = View.VISIBLE // } // } val viewStateObserver = Observer> {watchFrom -> if(watchFrom.isNotEmpty()){ watchHistoryRc.visibility = View.VISIBLE continueText.visibility = View.VISIBLE adapter.submitList(watchFrom) addRecyclerAnimation(watchHistoryRc,adapter) viewModel.currentFragment.observe(this){ if(it == R.id.searchFragment || it == R.id.TVShowDetails || it == R.id.animeDetailsFragment ){ watchHistoryRc.visibility = View.GONE continueText.visibility = View.GONE } else{ watchHistoryRc.visibility = View.VISIBLE continueText.visibility = View.VISIBLE } } // if(currentFragment.value == R.id.searchFragment){ // watchHistoryRc.visibility = View.GONE // continueText.visibility = View.GONE // }else // { // watchHistoryRc.visibility = View.VISIBLE // continueText.visibility = View.VISIBLE // } } else{ watchHistoryRc.visibility = View.GONE continueText.visibility = View.GONE } } viewModel.allWatchHistory.observe(this,viewStateObserver) viewModel.currentFragment.observe(this){ bottomNavigationView.selectedItemId = it } super.onResume() } private fun showDialog(){ val builder = AlertDialog.Builder(this) builder.setMessage("There is an update available to this app") .setTitle("Update Found") builder.setPositiveButton("Download"){ dialog, _ -> // User clicked OK button val url = "https://github.com/Sovan22/Tokeii/releases/" val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) startActivity(intent) dialog.dismiss() } builder.setNegativeButton("Cancel"){ _, _ -> // User cancelled the dialog } val dialog = builder.create() dialog.show() } override fun onBackPressed() { if(viewModel.currentFragment.value == R.id.searchFragment && viewModel.searchOpen.value == true) { viewModel.searchOpen.value = false } else super.onBackPressed() } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/Movie.kt ================================================ package com.demomiru.tokeiv2 import java.io.Serializable data class Movie( val id : String, val original_language:String, val production_countries:List, val title: String, val poster_path : String, val release_date : String ) : Serializable data class Prod( val iso_3166_1 : String, val name: String ) data class MovieArray( val movieFile : ArrayList ) data class MovieFile( val file: String, //episode file val title: String// language ) data class MovieIMDB( val id: String, val imdb_id : String, val external_ids: ExternalIDs ) data class Keys( val file: String, val key : String ) ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/MovieAdapter.kt ================================================ package com.demomiru.tokeiv2 import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.paging.PagingDataAdapter import androidx.paging.PagingSource import androidx.paging.PagingState import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import coil.load import com.demomiru.tokeiv2.utils.retrofitBuilder import com.demomiru.tokeiv2.utils.yearExtract class MovieAdapter(private val clickHandler : (Movie) -> Unit) : ListAdapter(differCallback) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.image_view) val titleTextView: TextView = itemView.findViewById(R.id.title_text_view) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view,parent,false) return ViewHolder(view) } companion object { val differCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem == newItem } } } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val movie = getItem(position) holder.titleTextView.text = movie.title + " (${yearExtract(movie.release_date)})" holder.imageView.load("https://image.tmdb.org/t/p/w500${movie.poster_path}") holder.itemView.setOnClickListener { clickHandler(movie) } } } class MoviesPagingSource(private val list: Int): PagingSource() { private val retrofit = retrofitBuilder() private val movieService = retrofit.create(MovieService::class.java) override suspend fun load(params: LoadParams): LoadResult { return try { val currentPage = params.key ?: 1 val response = when(list){ 1-> movieService.getPopularMovies(BuildConfig.TMDB_API_KEY,"en-US",currentPage) 2-> movieService.getTrendingMovies(BuildConfig.TMDB_API_KEY, "en-US",currentPage) 3-> movieService.getTopRatedMovies(BuildConfig.TMDB_API_KEY,"en-US",currentPage) else -> throw Exception("wrong list parameter input") } val data = response.body()!!.results val responseData = mutableListOf() responseData.addAll(data) LoadResult.Page( data = responseData, prevKey = if (currentPage == 1) null else -1, nextKey = currentPage.plus(1) ) } catch (e: Exception) { LoadResult.Error(e) } } override fun getRefreshKey(state: PagingState): Int? { return null } } class MovieAdapter2( private val clickHandler : (Movie) -> Unit ) : PagingDataAdapter(differCallback) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.image_view) val titleTextView: TextView = itemView.findViewById(R.id.title_text_view) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view,parent,false) return ViewHolder(view) } companion object { val differCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean { return oldItem == newItem } } } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: ViewHolder, position: Int) { val movie = getItem(position)!! holder.titleTextView.text = movie.title + " (${yearExtract(movie.release_date)})" holder.imageView.load("https://image.tmdb.org/t/p/w500${movie.poster_path}") holder.itemView.setOnClickListener { clickHandler(movie) } holder.setIsRecyclable(false) } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/MoviePlayActivity.kt ================================================ package com.demomiru.tokeiv2 import android.annotation.SuppressLint import android.content.Context import android.content.res.Resources import android.net.Uri import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.webkit.WebChromeClient import android.webkit.WebResourceRequest import android.webkit.WebResourceResponse import android.webkit.WebView import android.webkit.WebViewClient import android.widget.FrameLayout import android.widget.ProgressBar import android.widget.Toast import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope import androidx.media3.common.Player.* import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.demomiru.tokeiv2.extractors.PrMovies import com.demomiru.tokeiv2.extractors.ResultsAdapter import com.demomiru.tokeiv2.extractors.SearchResponse import com.demomiru.tokeiv2.utils.Extractor import com.demomiru.tokeiv2.utils.GoMovies import com.demomiru.tokeiv2.utils.GogoAnime import com.demomiru.tokeiv2.utils.SmashyStream import com.demomiru.tokeiv2.utils.SuperstreamUtils import com.demomiru.tokeiv2.utils.getMovieImdb import com.demomiru.tokeiv2.utils.getMovieLink import com.demomiru.tokeiv2.utils.getTvImdb import com.demomiru.tokeiv2.utils.getTvLink import com.demomiru.tokeiv2.utils.passVideoData import com.demomiru.tokeiv2.watching.ContinueWatching import com.demomiru.tokeiv2.watching.VideoData import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.Exception @Suppress("DEPRECATION") class MoviePlayActivity : AppCompatActivity(){ private val gson = Gson() private lateinit var webView : WebView private var fullscreenContainer: FullscreenHolder? = null private var animeEp : List = listOf() private var isSuper = false private var superId: Int? = null private var IMDBid: String? = null private val superStream = SuperstreamUtils() private lateinit var loading:ProgressBar private lateinit var id:String // private var clickedMiddle = false private lateinit var resultRc : RecyclerView private var animeUrl = "" private var season: Int = 1 private var episode: Int = 1 private var origin : String = "" private var year : String = "" private var seekProgress : Int = 0 private var imgLink : String? = null private lateinit var title:String private var type : String? = null private val videoUrl = MutableLiveData() private var subUrl : MutableList = mutableListOf() private var source : String? = null private val COVER_SCREEN_PARAMS = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) private val prMovies = PrMovies() private lateinit var url : String // private val args : MoviePlayActivityArgs by navArgs() @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_movie_play) Log.i("Start", "Time") resultRc = findViewById(R.id.results_rc) val bundle = intent.extras type = bundle?.getString("type") when (type) { "movie" -> { val data = bundle?.getSerializable("Data") as? Movie origin = data!!.original_language year = data.release_date.substringBefore("-") if(origin == "kn" || origin == "ml" || origin == "ta" || origin == "te") origin = "hi" id = data.id title = data.title imgLink = data.poster_path } "tvshow" -> { val data = bundle?.getSerializable("Data") as? Episode id = bundle?.getString("id")!! title = bundle.getString("showTitle")!! imgLink = bundle.getString("poster") season = data!!.season_number.toInt() episode = data.episode_number.toInt() } "anime" ->{ val data = bundle?.getParcelable("Data") as? GogoAnime.AnimeDetails id = bundle?.getString("id")!! title = data?.title!! imgLink = data.poster season = 1 episode = bundle.getInt("ep") animeEp = data.episodes!! animeUrl = data.episodes[episode].url } else -> { val data = bundle?.getParcelable("Data") as? ContinueWatching id = data!!.tmdbID.toString() title = data.title imgLink = data.imgLink seekProgress = data.progress type = data.type year = data.year ?: "" origin = data.origin ?: "" println(data) if (type != "movie"){ season = data.season episode = data.episode if(type == "anime"){ animeEp = data.animeEp!! animeUrl = data.animeEp[episode].url } } } } loading = findViewById(R.id.loading_content) webView = findViewById(R.id.web_view) loading.visibility = View.VISIBLE videoUrl.observe(this) { hlsUri -> if (!hlsUri.isNullOrEmpty()) { webView.visibility = View.GONE videoUrl.removeObservers(this) val intent = passVideoData(VideoData( seekProgress, imgLink!!, id.toInt(), IMDBid, title, episode, season, type!!, hlsUri, superId, subUrl, animeEp, origin, year ),this) intent.putExtra("origin", origin) intent.putExtra("superstream",isSuper) intent.putExtra("animeUrl",animeUrl) // println(source) intent.putExtra("source",source) startActivity(intent) finish() } } // if(type == "tvshow"){ // lifecycleScope.launch { // val imdbId = getTvImdb(id) // if(imdbId.isNotBlank()){ // origin = "hi" // IMDBid = imdbId // val link = getTvLink(imdbId,season-1,episode-1) // if(link.isNotBlank()) // videoUrl.value = link // else{ // withContext(Dispatchers.Main){ // Toast.makeText(this@MoviePlayActivity,"No Links Available", Toast.LENGTH_SHORT).show() // finish() // } // } // } // else{ // //Superstream add // val mainData = superStream.search(title) // superId = mainData.data.list[0].id // if (superId != null) { // isSuper = true // val tvLinks = superStream.loadLinks(false, superId!!, season, episode) // val urlMaps: MutableMap = mutableMapOf() // tvLinks.data?.list?.forEach { // if(!it.path.isNullOrBlank()){ // println("${it.quality} : ${it.path}") // urlMaps[it.quality!!] = it.path // if(it.quality == "720p") { // val subtitle = superStream.loadSubtile(false,it.fid!!,superId!!,season,episode).data //// // getSub(subtitle) // // return@forEach // } // } // } // if(urlMaps.isNotEmpty()) // videoUrl.value = gson.toJson(urlMaps) // if(videoUrl.value.isNullOrBlank()){ //// withContext(Dispatchers.Main){ //// Toast.makeText(this@MoviePlayActivity, "Not Available",Toast.LENGTH_SHORT).show() //// finish() //// } // isSuper = false // getGoMovieLink(false) //// getSmashLink(false) // } // } // else{ // isSuper = false // getGoMovieLink(false) //// getSmashLink(false) // } // // } // } // } if(type == "tvshow"){ lifecycleScope.launch (Dispatchers.IO){ val imdbId = getTvImdb(id) if (imdbId.isNotBlank()) { origin = "hi" IMDBid = imdbId } val links = Extractor(origin).loadExtractor(title,id,year,season,episode,false) println(links) val list = prMovies.getPrMovieLink(title) withContext(Dispatchers.Main) { if (!links.videoUrl.isNullOrBlank()) { println("if run") subUrl.addAll(links.subs) isSuper = links.isSuper source = links.source videoUrl.value = links.videoUrl } else { println("else run") loading.visibility = View.GONE try { val rcAdapter = ResultsAdapter { lifecycleScope.launch(Dispatchers.IO) { val src = prMovies.loadLinks(it.link!!) if (src.file.isNullOrBlank()) throw Exception("no video url found") withContext(Dispatchers.Main) { isSuper = false source = "prmovies" videoUrl.value = src.file } } } val width = Resources.getSystem().displayMetrics.widthPixels val dpi = Resources.getSystem().displayMetrics.densityDpi val grid = (width*160)/(165*dpi) resultRc.apply { layoutManager = GridLayoutManager(this@MoviePlayActivity,grid) adapter = rcAdapter } if (list.isEmpty()) throw Exception("Empty search") rcAdapter.submitList(list) }catch (e:Exception){ Toast.makeText( this@MoviePlayActivity, "No available links", Toast.LENGTH_SHORT ).show() finish() } } } } } else if(type == "movie"){ println("Reached here") // if (origin != "hi") { lifecycleScope.launch(Dispatchers.IO) { // try { // val mainData = superStream.search(title) //// println(mainData.data.list[0].year) // val item = mainData.data.list[0] // println(year + " ${item.year}") // superId = // if (item.title == title && item.year.toString() == year) item.id else null // getMovieEn() // }catch (e : Exception){ // getMovieEn() // } val links = Extractor(origin).loadExtractor(title,id,year, season,episode,true) println(links) val query = title.filter { it.isLetterOrDigit() || it.isWhitespace() } val list = prMovies.getPrMovieLink(query) withContext(Dispatchers.Main) { if (!links.videoUrl.isNullOrBlank()) { subUrl.addAll(links.subs) isSuper = links.isSuper source = links.source videoUrl.value = links.videoUrl } else { println("else run") println(list) loading.visibility = View.GONE try { if (list.isEmpty()) throw Exception("Empty search") val rcAdapter = ResultsAdapter { lifecycleScope.launch(Dispatchers.IO) { val src = prMovies.loadLinks(it.link!!) if (src.file.isNullOrBlank()) throw Exception("no video url found") withContext(Dispatchers.Main) { isSuper = false source = "prmovies" videoUrl.value = src.file } } } val width = Resources.getSystem().displayMetrics.widthPixels val dpi = Resources.getSystem().displayMetrics.densityDpi val grid = (width*160)/(165*dpi) resultRc.apply { layoutManager = GridLayoutManager(this@MoviePlayActivity,grid) adapter = rcAdapter } rcAdapter.submitList(list) }catch (e:Exception){ Toast.makeText( this@MoviePlayActivity, "No available links", Toast.LENGTH_SHORT ).show() finish() } // Toast.makeText( // this@MoviePlayActivity, // "No available links", // Toast.LENGTH_SHORT // ).show() // finish() } } // webView.loadUrl(url) } // } // else { // lifecycleScope.launch { // val imdbId = getMovieImdb(id) // IMDBid = imdbId // println(imdbId) // lifecycleScope.launch { // try { // val mainData = superStream.search(title) //// println(mainData.data.list[0].year) // val item = mainData.data.list[0] // println(year + " ${item.year}") // superId = // if (item.title == title && item.year.toString() == year) item.id else null // getMovieHi(imdbId) // } catch (e: Exception) { // getMovieHi(imdbId) // } // } // // videoUrl.value = getMovieLink(imdbId) // // // } // } } else{ lifecycleScope.launch (Dispatchers.IO){ val gogoSrc = GogoAnime() val link = gogoSrc.extractVideos(animeUrl) withContext(Dispatchers.Main){ videoUrl.value = link } } } } private fun getPrMovie(list: List){ } private suspend fun getGoMovieLink(isMovie: Boolean){ val goMovie = GoMovies() val data = goMovie.search(season,episode,title,isMovie,year) val vidLink = data.first val subLinks = data.second if(vidLink.isNullOrBlank()){ // Toast.makeText(this@MoviePlayActivity, "Not Available", Toast.LENGTH_SHORT).show() // finish() getSmashLink(isMovie) } else{ if (!subLinks.isNullOrEmpty())subUrl.add(subLinks) videoUrl.value = vidLink } } private fun getSub(subtitle: SuperstreamUtils.PrivateSubtitleData?){ subtitle?.list?.forEach { subList-> if(subList.language == "English"){ subList.subtitles.forEach { sub-> if (subUrl.size == 3) { return } if (sub.lang == "en" && !sub.file_path.isNullOrBlank()) { subUrl.add(sub.file_path) // println("${sub.language} : ${sub.file_path}") } } return } } } private fun getSub2(subtitle: SuperstreamUtils.PrivateSubtitleData?){ subtitle?.list?.forEach { subList-> subList.subtitles.forEach { sub-> if (!sub.file_path.isNullOrBlank()) { subUrl.add("${subList.language} :${sub.file_path}") } } } } private suspend fun getMovie() { if (superId != null) { isSuper = true val movieLinks = superStream.loadLinks(true, superId!!) movieLinks.data?.list?.forEach { if(!it.path.isNullOrBlank()){ println("${it.quality} : ${it.path}") if(it.quality == "720p") { val subtitle = superStream.loadSubtile(true,it.fid!!,superId!!).data // getSub(subtitle) videoUrl.value = it.path return } } } if(videoUrl.value.isNullOrBlank()){ withContext(Dispatchers.Main){ Toast.makeText(this@MoviePlayActivity, "Not Available",Toast.LENGTH_SHORT).show() finish() } } } else{ withContext(Dispatchers.Main){ Toast.makeText(this@MoviePlayActivity, "Not Available",Toast.LENGTH_SHORT).show() finish() } } } private suspend fun getMovieHi(imdbId: String) { if (superId != null) { isSuper = true val movieLinks = superStream.loadLinks(true, superId!!) val urlMaps: MutableMap = mutableMapOf() movieLinks.data?.list?.forEach { if(!it.path.isNullOrBlank()){ println("${it.quality} : ${it.path}") urlMaps[it.quality!!] = it.path if(it.quality == "720p") { val subtitle = superStream.loadSubtile(true,it.fid!!,superId!!).data // getSub(subtitle) return@forEach } } } if(urlMaps.isNotEmpty()) videoUrl.value = gson.toJson(urlMaps) if(videoUrl.value.isNullOrBlank()){ withContext(Dispatchers.Main){ // webView.loadUrl(url) // getSmashLink(true) isSuper = false val link = getMovieLink(imdbId) if (link.isBlank()) { getSmashLink(true,"hi") } else videoUrl.value = link } } } else{ withContext(Dispatchers.Main){ // webView.loadUrl(url) // getSmashLink(true) isSuper = false val link = getMovieLink(imdbId) if (link.isBlank()) getSmashLink(true,"hi") else videoUrl.value = link } } } private suspend fun getMovieEn() { if (superId != null) { isSuper = true val movieLinks = superStream.loadLinks(true, superId!!) val urlMaps: MutableMap = mutableMapOf() movieLinks.data?.list?.forEach { if(!it.path.isNullOrBlank()){ println("${it.quality} : ${it.path}") urlMaps[it.quality!!] = it.path if(it.quality == "720p") { val subtitle = superStream.loadSubtile(true,it.fid!!,superId!!).data // getSub(subtitle) return@forEach } } } if(urlMaps.isNotEmpty()) videoUrl.value = gson.toJson(urlMaps) if(videoUrl.value.isNullOrBlank()){ withContext(Dispatchers.Main){ // webView.loadUrl(url) // getSmashLink(true) isSuper = false getGoMovieLink(true) } } } else{ withContext(Dispatchers.Main){ // webView.loadUrl(url) // getSmashLink(true) isSuper = false getGoMovieLink(true) } } } private fun getSmashLink(isMovie:Boolean,src: String = "en") { val smashSrc = SmashyStream() lifecycleScope.launch { val links = smashSrc.getLink(isMovie,id, season, episode,src) val vidLink = links.first val subLink = links.second if(vidLink.isNullOrBlank()){ if (isMovie && origin == "hi"){ val mainData = superStream.search(title) superId = mainData.data.list[0].id getMovie() } else { withContext(Dispatchers.Main) { Toast.makeText(this@MoviePlayActivity, "Not Available", Toast.LENGTH_SHORT) .show() finish() } } } else{ if (!subLink.isNullOrBlank())subUrl.add(subLink) videoUrl.value = vidLink } } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) webView.saveState(outState) } override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { hideSystemUI() } } private fun hideSystemUI() { window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE // Set the content to appear under the system bars so that the // content doesn't resize when the system bars hide and show. or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // Hide the nav bar and status bar or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_FULLSCREEN) } } @Suppress("DEPRECATION") class FullscreenHolder(ctx: Context) : FrameLayout(ctx) { init { setBackgroundColor(ctx.resources.getColor(android.R.color.black)) } @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(evt: MotionEvent): Boolean { return true } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/MovieService.kt ================================================ package com.demomiru.tokeiv2 import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Query interface MovieService { @GET("movie/popular") suspend fun getPopularMovies( @Query("api_key") apiKey: String, @Query("language") language: String, @Query("page") page: Int ): Response @GET("trending/movie/day") suspend fun getTrendingMovies( @Query("api_key") apiKey: String, @Query("language") language: String, @Query("page") page: Int ): Response @GET("search/movie") suspend fun searchMovie( @Query("query") query: String, @Query("api_key") apiKey: String, @Query("language") language: String ) : Response // @GET("movie/{movie_id}") // suspend fun getImdbId( // @Path("movie_id") movie_id : String, // @Query("api_key") apiKey: String, // @Query("language") language: String // ) : Response // @GET("scrape") // Replace with the actual endpoint path // fun fetchDataFromServer( // @Header("ngrok-skip-browser-warning") value: String // ): Response @GET("movie/top_rated") suspend fun getTopRatedMovies( @Query("api_key") apiKey: String, @Query("language") language: String, @Query("page") page: Int ) : Response } //data class IdDB( // val id: String, // val imdb_id: String //) data class MovieResponse( val results: List ) //data class ServerResponse( // val videoLink: String // ) ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/MoviesFragment.kt ================================================ package com.demomiru.tokeiv2 import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope import androidx.paging.CombinedLoadStates import androidx.paging.LoadState import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.cachedIn import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.demomiru.tokeiv2.utils.ContinueWatchingViewModel2 import com.demomiru.tokeiv2.utils.addRecyclerAnimation import com.demomiru.tokeiv2.utils.passData import com.demomiru.tokeiv2.utils.retrofitBuilder import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.withContext // TODO: Rename parameter arguments, choose names that match // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private const val ARG_PARAM1 = "param1" private const val ARG_PARAM2 = "param2" /** * A simple [Fragment] subclass. * Use the [MoviesFragment.newInstance] factory method to * create an instance of this fragment. */ class MoviesFragment : Fragment() { // TODO: Rename and change types of parameters private var param1: String? = null private val activityViewModel: ContinueWatchingViewModel2 by activityViewModels() private lateinit var popMovieRc: RecyclerView private lateinit var trenMovieRc : RecyclerView private lateinit var topMovieRc : RecyclerView private var loading = MutableLiveData(true) private val adapter = MovieAdapter2{ println(it.release_date) startActivity(passData(it,requireContext())) } private var param2: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { param1 = it.getString(ARG_PARAM1) param2 = it.getString(ARG_PARAM2) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { lifecycleScope.launch (Dispatchers.IO){ } super.onViewCreated(view, savedInstanceState) } @DelicateCoroutinesApi override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment val view = inflater.inflate(R.layout.fragment_movies, container, false) popMovieRc = view.findViewById(R.id.movie_recycler_view) trenMovieRc = view.findViewById(R.id.trending_movie_rc) topMovieRc = view.findViewById(R.id.topRatedMovie_recycler_view) topMovieRc.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL,false) trenMovieRc.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL,false) popMovieRc.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL,false) val retrofit = retrofitBuilder() val movieService = retrofit.create(MovieService::class.java) lifecycleScope.launch(Dispatchers.Main) { withContext(Dispatchers.IO){ // val trmovies = Pager(PagingConfig(1)){MoviesPagingSource(3)}.flow.cachedIn(lifecycleScope) val tradapter = MovieAdapter2{ // val action = MoviesFragmentDirections.actionMoviesFragmentToMoviePlayActivity(it.id, "movie") // findNavController().navigate(play(it)) println(it.release_date) startActivity(passData(it, requireContext())) } // val tmovies = Pager(PagingConfig(1)){MoviesPagingSource(2)}.flow.cachedIn(lifecycleScope) val tadapter = MovieAdapter2 { // findNavController().navigate(play(it)) println(it.release_date) startActivity(passData(it, requireContext())) } // val movies = Pager(PagingConfig(1)){MoviesPagingSource(1)}.flow.cachedIn(lifecycleScope) val adapter = MovieAdapter2 { println(it.release_date) startActivity(passData(it, requireContext())) } withContext(Dispatchers.Main) { addRecyclerAnimation(topMovieRc, tradapter) addRecyclerAnimation(trenMovieRc, tadapter) addRecyclerAnimation(popMovieRc, adapter) lifecycleScope.launch { activityViewModel.topMovies.collect { tradapter.submitData(it) } } lifecycleScope.launch { activityViewModel.popMovies.collect{ adapter.submitData(it) } } lifecycleScope.launch { tadapter.addLoadStateListener { val state = it.refresh val visible = state is LoadState.Loading if(visible){ view.findViewById(R.id.loading_movies).visibility = View.VISIBLE } else{ view.findViewById(R.id.trending_text).visibility = View.VISIBLE view.findViewById(R.id.movies_text).visibility = View.VISIBLE view.findViewById(R.id.topmovies_text).visibility = View.VISIBLE view.findViewById(R.id.loading_movies).visibility = View.GONE } } // view.findViewById(R.id.loading_movies).visibility = View.GONE // view.findViewById(R.id.trending_text).visibility = View.VISIBLE // view.findViewById(R.id.movies_text).visibility = View.VISIBLE // view.findViewById(R.id.topmovies_text).visibility = View.VISIBLE activityViewModel.trenMovies.collect { tadapter.submitData(it) } } } // } // withContext(Dispatchers.Main) { // view.findViewById(R.id.loading_movies).visibility = View.GONE // view.findViewById(R.id.trending_text).visibility = View.VISIBLE // view.findViewById(R.id.movies_text).visibility = View.VISIBLE // view.findViewById(R.id.topmovies_text).visibility = View.VISIBLE // } } return view } // private inline fun CombinedLoadStates.decideOnState( // showLoading: (Boolean) -> Unit, // showEmptyState: (Boolean) -> Unit, // showError: (String) -> Unit // ) { // showLoading(refresh is LoadState.Loading) // // showEmptyState( // source.append is LoadState.NotLoading // && source.append.endOfPaginationReached // && adapter.itemCount == 0 // ) // // val errorState = source.append as? LoadState.Error // ?: source.prepend as? LoadState.Error // ?: source.refresh as? LoadState.Error // ?: append as? LoadState.Error // ?: prepend as? LoadState.Error // ?: refresh as? LoadState.Error // // errorState?.let { showError(it.error.toString()) } // } override fun onResume() { activityViewModel.currentFragment.value = R.id.moviesFragment super.onResume() } companion object { /** * Use this factory method to create a new instance of * this fragment using the provided parameters. * * @param param1 Parameter 1. * @param param2 Parameter 2. * @return A new instance of fragment MoviesFragment. */ // TODO: Rename and change types and number of parameters @JvmStatic fun newInstance(param1: String, param2: String) = MoviesFragment().apply { arguments = Bundle().apply { putString(ARG_PARAM1, param1) putString(ARG_PARAM2, param2) } } } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/SearchFragment.kt ================================================ @file:OptIn(DelicateCoroutinesApi::class) package com.demomiru.tokeiv2 import android.content.Context import android.content.res.Resources import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.RadioButton import android.widget.RadioGroup import android.widget.SearchView import android.widget.TextView import android.widget.Toast import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.demomiru.tokeiv2.anime.AnimeAdapter import com.demomiru.tokeiv2.history.QueryRepository import com.demomiru.tokeiv2.history.SearchDatabase import com.demomiru.tokeiv2.history.SearchHistory import com.demomiru.tokeiv2.history.SearchHistoryAdapter2 import com.demomiru.tokeiv2.utils.ContinueWatchingViewModel2 import com.demomiru.tokeiv2.utils.SearchVMFactory import com.demomiru.tokeiv2.utils.SearchViewModel import com.demomiru.tokeiv2.utils.addRecyclerAnimation import com.demomiru.tokeiv2.utils.passData import kotlinx.coroutines.DelicateCoroutinesApi class SearchFragment : Fragment() { // private lateinit var searchHistoryList: List private lateinit var searchHistoryRC: RecyclerView private lateinit var searchView : SearchView private val activityViewModel: ContinueWatchingViewModel2 by activityViewModels() private lateinit var searchHistoryFl : LinearLayout private lateinit var deleteAll : TextView private var isClicked = true private lateinit var adapter: SearchHistoryAdapter2 private lateinit var searchResultsRc : RecyclerView private lateinit var searchEt: EditText private lateinit var movieChoice : RadioButton private lateinit var tvChoice : RadioButton private lateinit var animeChoice: RadioButton private lateinit var choice : RadioGroup private lateinit var searchLoading : ProgressBar private val database by lazy { SearchDatabase.getInstance(requireContext()) } private val searchHistoryDao by lazy { database.searchDao() } private lateinit var queryRepository:QueryRepository private lateinit var viewModelFactory: SearchVMFactory private lateinit var mAdapter : MovieAdapter private lateinit var tvAdapter : TVShowAdapter private lateinit var aAdapter: AnimeAdapter private val viewModel: SearchViewModel by viewModels( factoryProducer = { viewModelFactory } ) override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment val view = inflater.inflate(R.layout.fragment_search, container, false) searchHistoryFl = view.findViewById(R.id.search_history_ll) searchView = view.findViewById(R.id.searchView) queryRepository = QueryRepository(searchHistoryDao) viewModelFactory = SearchVMFactory(queryRepository) deleteAll = view.findViewById(R.id.delete_all_button) searchHistoryRC = view.findViewById(R.id.search_history_rc) searchResultsRc = view.findViewById(R.id.search_results_rc) searchLoading = view.findViewById(R.id.search_loading) movieChoice = view.findViewById(R.id.movies_search) tvChoice = view.findViewById(R.id.tvShows_search) animeChoice = view.findViewById(R.id.anime_search) choice = view.findViewById(R.id.choice) activityViewModel.currentFragment.value = R.id.searchFragment // println(activityViewModel.test) mAdapter = MovieAdapter{ // val action = SearchFragmentDirections.actionSearchFragmentToMoviePlayActivity(it.id,"movie",title = it.title) // findNavController().navigate(action) startActivity(passData(it, requireContext())) } tvAdapter = TVShowAdapter{it, _ -> val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetails(it.id, title = it.name) findNavController().navigate(action) } aAdapter = AnimeAdapter(requireContext()){ // val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetails( // encodeStringToInt(it.name).toString(), title = "",animeUrl = it.url) // findNavController().navigate(action) val action = SearchFragmentDirections.actionSearchFragmentToAnimeDetailsFragment(it.name,it.url) findNavController().navigate(action) } // var start = true // viewModel.queryText.observe(viewLifecycleOwner){ // if(it.isNotBlank() && start)searchView.setQuery(it,false) // } // searchView.setQuery(viewModel.queryText.value,false) val width = Resources.getSystem().displayMetrics.widthPixels val dpi = Resources.getSystem().displayMetrics.densityDpi val grid = (width*160)/(165*dpi) println(grid) searchResultsRc.layoutManager = GridLayoutManager(requireContext(),grid) searchHistoryRC.layoutManager = LinearLayoutManager(requireContext()) // start = false adapter = SearchHistoryAdapter2{it,search-> if (search){ searchView.setQuery(it.query,true) viewModel.queryText.value = it.query viewModel.searchClicked.value = true // (activity as MainActivity).triggerSearchKeyPress() } else { viewModel.deleteRecord(it) } } searchHistoryRC.adapter = adapter searchHistoryRC.visibility = View.VISIBLE // GlobalScope.launch (Dispatchers.IO){ // queryRepository.loadData() // } val searchHistoryObserver = Observer>{ adapter.submitList(it) if (it.isEmpty()){ // searchHistoryRC.visibility = View.GONE // deleteAll.visibility = View.GONE searchHistoryFl.visibility = View.GONE activityViewModel.searchOpen.value = false isClicked = true } } viewModel.searchClicked.observe(viewLifecycleOwner){ if(it){ // deleteAll.visibility = View.GONE // searchHistoryRC.visibility = View.GONE searchHistoryFl.visibility = View.GONE activityViewModel.searchOpen.value = false } else{ // deleteAll.visibility = View.VISIBLE // searchHistoryRC.visibility = View.VISIBLE searchHistoryFl.visibility = View.VISIBLE activityViewModel.searchOpen.value = true } } // queryRepository.allQueries.observe(viewLifecycleOwner,searchHistoryObserver) viewModel.queries.observe(viewLifecycleOwner,searchHistoryObserver) deleteAll.setOnClickListener{ viewModel.deleteAll() } searchEt = view.findViewById(R.id.search_et) searchEt.setOnClickListener{ if(isClicked) { deleteAll.visibility = View.VISIBLE searchHistoryRC.visibility = View.VISIBLE isClicked = false } else{ deleteAll.visibility = View.GONE searchHistoryRC.visibility = View.GONE isClicked = true } } // searchView.queryHint = "Search" // searchView.setOnCloseListener { // deleteAll.visibility = View.GONE // searchHistoryRC.visibility = View.GONE // false // } searchView.setOnClickListener { val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(it.findFocus(), 0) } // viewModel.queryText.observe(viewLifecycleOwner){ // if(it.isNotBlank())searchView.setQuery(it,false) // } searchView.setOnQueryTextFocusChangeListener { view, hasFocus -> if(hasFocus) { // deleteAll.visibility = View.VISIBLE // searchHistoryRC.visibility = View.VISIBLE view.postDelayed({ val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(view.findFocus(), 0) }, 200) searchHistoryFl.visibility = View.VISIBLE activityViewModel.searchOpen.value = true } // else{ // // deleteAll.visibility = View.GONE // viewModel.searchClicked.value = true // searchHistoryRC.visibility = View.GONE // // } } activityViewModel.searchOpen.observe(viewLifecycleOwner){ if(it == false){ searchHistoryFl.visibility = View.GONE searchView.clearFocus() } } searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { // Logic for when search button is clicked if(query.isNullOrEmpty())return false viewModel.searchClicked.value = true if(movieChoice.isChecked) { viewModel.choice.value = 1 performMovieSearch(query) } else if(tvChoice.isChecked){ viewModel.choice.value = 2 performShowSearch(query) } else{ viewModel.choice.value = 3 performAnimeSearch(query) } // searchView.isIconified = true // searchView.queryHint = query viewModel.queryText.value = query searchHistoryFl.visibility = View.GONE searchView.clearFocus() return false } override fun onQueryTextChange(newText: String?): Boolean { // Logic for when text in search view changes //add filter in searchHistory Rc // deleteAll.visibility = View.VISIBLE // searchHistoryRC.visibility = View.VISIBLE searchHistoryFl.visibility = View.VISIBLE viewModel.queryText.value = newText?:"" return false } }) // searchView.setOnClickListener { // searchView.isIconified = false // } // searchEt.setOnKeyListener { _, actionId, event -> // if(searchEt.text.toString().isEmpty()) return@setOnKeyListener true // if (actionId == KeyEvent.ACTION_DOWN || event.keyCode == KeyEvent.KEYCODE_ENTER) { // // The "Search" button on the keyboard was clicked // viewModel.searchClicked.value = true // if(movieChoice.isChecked) // { // viewModel.choice.value = 1 // performMovieSearch() // } // else if(tvChoice.isChecked){ // viewModel.choice.value = 2 // performShowSearch() // } // else{ // viewModel.choice.value = 3 // performAnimeSearch() // } // return@setOnKeyListener true // } // false // } choice.setOnCheckedChangeListener { _, _: Int -> if(movieChoice.isChecked) { viewModel.choice.value = 1 } else if(tvChoice.isChecked){ viewModel.choice.value = 2 } else{ viewModel.choice.value = 3 } searchResultsRc.visibility = View.GONE // searchHistoryRC.visibility = View.VISIBLE searchHistoryFl.visibility = View.VISIBLE viewModel.searchClicked.value = false } when(viewModel.choice.value){ 1 -> viewModel.movieList.observe(viewLifecycleOwner) { movies -> if (movies.isNotEmpty()) { mAdapter.submitList(movies) addRecyclerAnimation(searchResultsRc,mAdapter) searchLoading.visibility = View.GONE searchResultsRc.visibility = View.VISIBLE } } 2 -> viewModel.tvList.observe(viewLifecycleOwner){shows-> if(shows.isNotEmpty()){ tvAdapter.submitList(shows) addRecyclerAnimation(searchResultsRc,tvAdapter) searchLoading.visibility = View.GONE searchResultsRc.visibility = View.VISIBLE } } 3 -> viewModel.animeList.observe(viewLifecycleOwner){ anime-> if(anime.isNotEmpty()){ aAdapter.submitList(anime) addRecyclerAnimation(searchResultsRc,aAdapter) searchLoading.visibility = View.GONE searchResultsRc.visibility = View.VISIBLE } } else -> println(viewModel.choice.value) } viewModel.noMatches.observe(viewLifecycleOwner){ if(it) { Toast.makeText(requireContext(), "No Matches", Toast.LENGTH_SHORT).show() viewModel.noMatches.value = false } } return view } private fun performAnimeSearch(query: String) { searchResultsRc.visibility = View.GONE searchHistoryFl.visibility = View.GONE isClicked = true // deleteAll.visibility = View.GONE searchLoading.visibility = View.VISIBLE // val gogoSrc = GogoAnime() // val query = searchEt.text.toString() val history = SearchHistory(query = query) viewModel.addToHistory(history) viewModel.searchAnime(query) // val adapter = AnimeAdapter(requireContext()){ // lifecycleScope.launch { // //// val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetails( //// encodeStringToInt(it.name).toString(), title = "",animeUrl = it.url) //// findNavController().navigate(action) // // val action = SearchFragmentDirections.actionSearchFragmentToAnimeDetailsFragment(it.name,it.url) // findNavController().navigate(action) // // } // } viewModel.animeList.observe(viewLifecycleOwner){anime-> if(anime.isNotEmpty()){ aAdapter.submitList(anime) addRecyclerAnimation(searchResultsRc, aAdapter) searchLoading.visibility = View.GONE searchResultsRc.visibility = View.VISIBLE searchResultsRc.requestFocus() } else{ // Toast.makeText(requireContext(),"No Matches",Toast.LENGTH_SHORT).show() searchLoading.visibility = View.GONE searchResultsRc.visibility = View.GONE } } // addRecyclerAnimation(searchResultsRc, aAdapter) // lifecycleScope.launch(Dispatchers.IO) { // val history = SearchHistory(query = query) // queryRepository.insert(history) // queryRepository.loadData() // val animeList = gogoSrc.search(query) // // withContext(Dispatchers.Main) // { // if (animeList.isEmpty()) Toast.makeText(requireContext(),"No Matches",Toast.LENGTH_LONG).show() // adapter.submitList(animeList) // searchLoading.visibility = View.GONE // searchResultsRc.visibility = View.VISIBLE // } // } } @OptIn(DelicateCoroutinesApi::class) private fun performMovieSearch(query:String) { searchResultsRc.visibility = View.GONE // searchHistoryRC.visibility = View.GONE isClicked = true // deleteAll.visibility = View.GONE searchHistoryFl.visibility = View.GONE searchLoading.visibility = View.VISIBLE // val query = searchEt.text.toString() // GlobalScope.launch(Dispatchers.IO) { // // val history = SearchHistory(query = query) // queryRepository.insert(history) // queryRepository.loadData() // } val history = SearchHistory(query = query) viewModel.addToHistory(history) // val retrofit = retrofitBuilder() // // val movieService = retrofit.create(MovieService::class.java) viewModel.searchMovie(query) viewModel.movieList.observe(viewLifecycleOwner){movies-> if(movies.isNotEmpty()){ mAdapter.submitList(movies) addRecyclerAnimation(searchResultsRc,mAdapter) searchLoading.visibility = View.GONE searchResultsRc.visibility = View.VISIBLE searchResultsRc.requestFocus() } else{ // Toast.makeText(requireContext(),"No Matches",Toast.LENGTH_SHORT).show() searchLoading.visibility = View.GONE searchResultsRc.visibility = View.GONE } } // GlobalScope.launch (Dispatchers.Main){ // // val searchResults = movieService.searchMovie( // query, // BuildConfig.TMDB_API_KEY, // "en-US" // ) // // if (searchResults.isSuccessful) // { // val movies = searchResults.body()?.results ?: emptyList() // if (movies.isEmpty()) Toast.makeText(requireContext(),"No Matches",Toast.LENGTH_LONG).show() // val adapter = MovieAdapter(movies){ //// val action = SearchFragmentDirections.actionSearchFragmentToMoviePlayActivity(it.id,"movie",title = it.title) //// findNavController().navigate(action) // startActivity(passData(it,requireContext())) // } // addRecyclerAnimation(searchResultsRc,adapter) // } // // withContext(Dispatchers.Main) { // searchLoading.visibility = View.GONE // searchResultsRc.visibility = View.VISIBLE // } // } } @OptIn(DelicateCoroutinesApi::class) private fun performShowSearch(query: String) { searchResultsRc.visibility = View.GONE // searchHistoryRC.visibility = View.GONE searchHistoryFl.visibility = View.GONE isClicked = true searchLoading.visibility = View.VISIBLE // deleteAll.visibility = View.GONE // val query = searchEt.text.toString() // GlobalScope.launch(Dispatchers.IO) { //// searchHistoryDao.deleteAll() // val history = SearchHistory(query = query) // queryRepository.insert(history) // queryRepository.loadData() // } val history = SearchHistory(query = query) viewModel.addToHistory(history) viewModel.searchTv(query) viewModel.tvList.observe(viewLifecycleOwner){shows-> if(shows.isNotEmpty()){ tvAdapter.submitList(shows) addRecyclerAnimation(searchResultsRc,tvAdapter) searchLoading.visibility = View.GONE searchResultsRc.visibility = View.VISIBLE searchResultsRc.requestFocus() } else{ // Toast.makeText(requireContext(),"No Matches",Toast.LENGTH_SHORT).show() searchLoading.visibility = View.GONE searchResultsRc.visibility = View.GONE } } // val retrofit = retrofitBuilder() // // val tvService = retrofit.create(TMDBService::class.java) // GlobalScope.launch (Dispatchers.Main){ // // val searchResults = tvService.searchShow( // query, // BuildConfig.TMDB_API_KEY, // "en-US" // ) // // if (searchResults.isSuccessful) // { // val tvShows = searchResults.body()?.results ?: emptyList() // if (tvShows.isEmpty()) Toast.makeText(requireContext(),"No Matches",Toast.LENGTH_LONG).show() // val adapter = TVShowAdapter(tvShows){it, _ -> // val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetails(it.id, title = it.name) // findNavController().navigate(action) // } // addRecyclerAnimation(searchResultsRc,adapter) // } // withContext(Dispatchers.Main) { // searchLoading.visibility = View.GONE // searchResultsRc.visibility = View.VISIBLE // } // } } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/TMDBService.kt ================================================ package com.demomiru.tokeiv2 import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.Query import java.io.Serializable interface TMDBService { @GET("tv/popular") suspend fun getPopularTVShows( @Query("api_key") apiKey: String, @Query("language") language: String, @Query("page") page: Int ): Response @GET("trending/tv/day") suspend fun getTrendingTVShows( @Query("api_key") apiKey: String, @Query("language") language: String, @Query("page") page: Int ): Response @GET("tv/{series_id}") suspend fun getTVShowDetails( @Path("series_id") seriesID: String, @Query("api_key") apiKey: String, @Query("language") language: String ): Response @GET("tv/{series_id}/season/{season_number}") suspend fun getEpisodeDetails( @Path("series_id") seriesID: String, @Path("season_number") season: String, @Query("api_key") apiKey: String, @Query("language") language: String ) : Response @GET("search/tv") suspend fun searchShow( @Query("query") query: String, @Query("api_key") apiKey: String, @Query("language") language: String ) : Response @GET("tv/top_rated") suspend fun getTopRatedTVShows( @Query("api_key") apiKey: String, @Query("language") language: String, @Query("page") page: Int ) : Response } data class TVShowResponse( val results: List ) data class TVShowDetailsResponse( val backdrop_path : String, val overview : String, val original_name : String, val number_of_seasons: String, val poster_path : String, val number_of_episodes : Int, val tagline : String ) data class TVShowEpisodeDetailsResponse( val episodes : List ) data class Episode( val air_date: String?, val season_number: String, val episode_number: String, val overview: String?, val name: String?, val still_path: String?, ) : Serializable data class Season( val id : String, val title:String, val folder: List ) data class Folder( val episode: String, val folder: List ) data class EpisodeID( val file: String, //episode file val title: String// language ) data class TvIMDB( val languages: List, val external_ids : ExternalIDs, val number_of_seasons: String, val origin_country: List ) data class ExternalIDs( val imdb_id : String ) data class ImdbEpisode( val episodes : List ) data class EP( val episode_number: String ) ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/TVShowAdapter.kt ================================================ package com.demomiru.tokeiv2 import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.core.view.ViewCompat import androidx.paging.PagingDataAdapter import androidx.paging.PagingSource import androidx.paging.PagingState import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import coil.load import com.demomiru.tokeiv2.MovieAdapter2.Companion.differCallback import com.demomiru.tokeiv2.utils.retrofitBuilder class TVShowAdapter( private val clickHandler : (TVshow,Int) -> Unit ) : ListAdapter(differCallback) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.image_view) val titleTextView: TextView = itemView.findViewById(R.id.title_text_view) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_view, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val tvShow = getItem(position) holder.titleTextView.text = tvShow.name holder.imageView .load("https://image.tmdb.org/t/p/w500${tvShow.poster_path}") ViewCompat.setTransitionName(holder.imageView, "image_$position") holder.itemView.setOnClickListener { clickHandler(tvShow,position) } } companion object { val differCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: TVshow, newItem: TVshow): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: TVshow, newItem: TVshow): Boolean { return oldItem == newItem } } } } class TvShowPagingSource(private val list: Int): PagingSource() { private val retrofit = retrofitBuilder() private val tvService = retrofit.create(TMDBService::class.java) override suspend fun load(params: LoadParams): LoadResult { return try { val currentPage = params.key ?: 1 val response = when(list){ 1-> tvService.getPopularTVShows(BuildConfig.TMDB_API_KEY,"en-US",currentPage) 2-> tvService.getTrendingTVShows(BuildConfig.TMDB_API_KEY, "en-US",currentPage) 3-> tvService.getTopRatedTVShows(BuildConfig.TMDB_API_KEY,"en-US",currentPage) else -> throw Exception("wrong list parameter input") } val data = response.body()!!.results val responseData = mutableListOf() responseData.addAll(data) LoadResult.Page( data = responseData, prevKey = if (currentPage == 1) null else -1, nextKey = currentPage.plus(1) ) } catch (e: Exception) { LoadResult.Error(e) } } override fun getRefreshKey(state: PagingState): Int? { return null } } class TVShowAdapter2(private val clickHandler : (TVshow,Int) -> Unit) : PagingDataAdapter(differCallback) { class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val imageView: ImageView = itemView.findViewById(R.id.image_view) val titleTextView: TextView = itemView.findViewById(R.id.title_text_view) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_view, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val tvShow = getItem(position)!! holder.titleTextView.text = tvShow.name holder.imageView .load("https://image.tmdb.org/t/p/w500${tvShow.poster_path}") ViewCompat.setTransitionName(holder.imageView, "image_$position") holder.itemView.setOnClickListener { clickHandler(tvShow,position) } holder.setIsRecyclable(false) } companion object { val differCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: TVshow, newItem: TVshow): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: TVshow, newItem: TVshow): Boolean { return oldItem == newItem } } } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/TVShowCardAdapter.kt ================================================ package com.demomiru.tokeiv2 import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import coil.load class TVShowCardAdapter(private val tvShows: List>): RecyclerView.Adapter() { class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ val verticalImage: ImageView = itemView.findViewById(R.id.vertical_container) val horizontalImage1: ImageView = itemView.findViewById(R.id.horizontal_container1) val horizontalImage2: ImageView = itemView.findViewById(R.id.horizontal_container2) } override fun onCreateViewHolder( parent: ViewGroup, viewType: Int ): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.card_view,parent,false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val tvShow = tvShows[position % tvShows.size] holder.verticalImage.load("https://image.tmdb.org/t/p/w500${tvShow[0].poster_path}") holder.horizontalImage1.load("https://image.tmdb.org/t/p/original${tvShow[1].backdrop_path}") holder.horizontalImage2.load("https://image.tmdb.org/t/p/original${tvShow[2].backdrop_path}") } override fun getItemCount(): Int { return Int.MAX_VALUE } } ================================================ FILE: app/src/main/java/com/demomiru/tokeiv2/TVShowDetails.kt ================================================ package com.demomiru.tokeiv2 import android.annotation.SuppressLint import android.content.res.Configuration 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 android.view.animation.AnimationUtils import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Button import android.widget.ImageView import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.Spinner import android.widget.TextView import android.widget.Toast import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.load import com.demomiru.tokeiv2.anime.AnimeEpisodeAdapter import com.demomiru.tokeiv2.utils.ContinueWatchingViewModel import com.demomiru.tokeiv2.utils.ContinueWatchingViewModel2 import com.demomiru.tokeiv2.utils.ContinueWatchingViewModelFactory import com.demomiru.tokeiv2.utils.GogoAnime import com.demomiru.tokeiv2.utils.dateToUnixTime import com.demomiru.tokeiv2.utils.dropDownMenu import com.demomiru.tokeiv2.utils.passData import com.demomiru.tokeiv2.utils.retrofitBuilder import com.demomiru.tokeiv2.watching.ContinueWatching import com.demomiru.tokeiv2.watching.ContinueWatchingDatabase import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class TVShowDetails : Fragment() { private val args : TVShowDetailsArgs by navArgs() private val activityViewModel: ContinueWatchingViewModel2 by activityViewModels() private lateinit var id: String private lateinit var title: String private var isAnime: Boolean = false private var animeDetails = MutableLiveData(null) private lateinit var viewModelFactory: ContinueWatchingViewModelFactory private val viewModel: ContinueWatchingViewModel by viewModels( factoryProducer = { viewModelFactory } ) private var lastPlayedSeason = 1 private lateinit var episodeProgress : ContinueWatching private val database by lazy { ContinueWatchingDatabase.getInstance(requireContext()) } private val watchHistoryDao by lazy { database.watchDao() } private lateinit var episodesRc: RecyclerView private lateinit var progressBar: ProgressBar private lateinit var dropDownSpinner: Spinner @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val viewStateObserver = Observer {watchFrom -> val continueButton = view.findViewById