diff --git a/.idea/misc.xml b/.idea/misc.xml index 9d11f06e0..afbdd9bb4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,6 +5,7 @@ + @@ -12,10 +13,12 @@ + + diff --git a/app/build.gradle b/app/build.gradle index e6d881642..a7f609a82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,5 +50,6 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-jackson:2.9.0' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.0' } \ No newline at end of file diff --git a/app/src/main/java/xyz/btcland/libretube/MainActivity.kt b/app/src/main/java/xyz/btcland/libretube/MainActivity.kt index a0c0aa405..782b3b93b 100644 --- a/app/src/main/java/xyz/btcland/libretube/MainActivity.kt +++ b/app/src/main/java/xyz/btcland/libretube/MainActivity.kt @@ -3,23 +3,51 @@ package xyz.btcland.libretube import android.content.res.Configuration import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import android.view.Menu +import android.view.MenuItem import android.widget.FrameLayout import androidx.constraintlayout.motion.widget.MotionLayout import androidx.fragment.app.Fragment +import androidx.navigation.Navigation import com.google.android.material.bottomnavigation.BottomNavigationView import androidx.navigation.findNavController +import androidx.navigation.ui.NavigationUI.onNavDestinationSelected import androidx.navigation.ui.setupWithNavController import com.google.android.exoplayer2.ExoPlayer class MainActivity : AppCompatActivity() { - lateinit var exoPlayer:ExoPlayer + lateinit var bottomNavigationView: BottomNavigationView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val bottomNavigationView = findViewById(R.id.bottomNav) + bottomNavigationView = findViewById(R.id.bottomNav) val navController = findNavController(R.id.fragment) bottomNavigationView.setupWithNavController(navController) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId){ + R.id.action_search -> { + val navController = findNavController(R.id.fragment) + navController.popBackStack() + navController.navigate(R.id.searchFragment) + //bottomNavigationView.clearFocus() + //val navController = findNavController(R.id.fragment) + //navController.navigate(R.id.searchFragment) + //navController.navigate(R.id.home2) + true + } + R.id.action_settings -> { + + true + } + else -> { + super.onOptionsItemSelected(item) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.action_bar,menu) + return true } override fun onConfigurationChanged(newConfig: Configuration) { diff --git a/app/src/main/java/xyz/btcland/libretube/PipedApi.kt b/app/src/main/java/xyz/btcland/libretube/PipedApi.kt index 939a58a6f..19bbbcb65 100644 --- a/app/src/main/java/xyz/btcland/libretube/PipedApi.kt +++ b/app/src/main/java/xyz/btcland/libretube/PipedApi.kt @@ -12,4 +12,13 @@ interface PipedApi { @GET("streams/{videoId}") suspend fun getStreams(@Path("videoId") videoId: String): Streams + + @GET("search") + suspend fun getSearchResults( + @Query("q") searchQuery: String, + @Query("filter") filer: String + ): List + + @GET("suggestions") + suspend fun getSuggestions(@Query("query") query: String): List } \ No newline at end of file diff --git a/app/src/main/java/xyz/btcland/libretube/PlayerFragment.kt b/app/src/main/java/xyz/btcland/libretube/PlayerFragment.kt index 789b58fcd..5238831d7 100644 --- a/app/src/main/java/xyz/btcland/libretube/PlayerFragment.kt +++ b/app/src/main/java/xyz/btcland/libretube/PlayerFragment.kt @@ -230,6 +230,7 @@ class PlayerFragment : Fragment() { override fun onStop() { super.onStop() try { + (activity as MainActivity).supportActionBar?.show() exoPlayer.stop() }catch (e: Exception){} @@ -256,135 +257,7 @@ class PlayerFragment : Fragment() { } private fun fetchJson(view: View) { - //val client = OkHttpClient() - fun run() { -/* val request = Request.Builder() - .url("http://piped-api.alefvanoon.xyz/streams/$videoId") - .build() -*//* val retrofit = Retrofit.Builder() - .baseUrl("http://piped-api.alefvanoon.xyz/") - .addConverterFactory(JacksonConverterFactory.create()) - .build() - - val videoInPlayer2 = retrofit.create(vidVid::class.java).vidIn(videoId)*//* - - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - e.printStackTrace() - } - override fun onResponse(call: Call, response: Response) { - response.use { - if (!response.isSuccessful) throw IOException("Unexpected code $response") - val body = response.body!!.string() - println(body) - val gson = GsonBuilder().create() - val videoInPlayer = gson.fromJson(body, VideoInPlayer::class.java) - var videosNameArray: Array = arrayOf() - videosNameArray += "HLS" - for (vid in videoInPlayer.videoStreams){ - val name = vid.quality +" "+ vid.format - videosNameArray += name - } - runOnUiThread { - var subtitle = mutableListOf() - if(videoInPlayer.subtitles.isNotEmpty()){ - subtitle?.add(SubtitleConfiguration.Builder(videoInPlayer.subtitles[0].url.toUri()) - .setMimeType(videoInPlayer.subtitles[0].mimeType) // The correct MIME type (required). - .setLanguage(videoInPlayer.subtitles[0].code) // The subtitle language (optional). - .build())} - val mediaItem: MediaItem = MediaItem.Builder() - .setUri(videoInPlayer.hls) - .setSubtitleConfigurations(subtitle) - .build() - exoPlayer = ExoPlayer.Builder(view.context) - .build() - exoPlayerView.setShowSubtitleButton(true) - exoPlayerView.setShowNextButton(false) - exoPlayerView.setShowPreviousButton(false) - //exoPlayerView.controllerShowTimeoutMs = 1500 - exoPlayerView.controllerHideOnTouch = true - exoPlayerView.player = exoPlayer - exoPlayer.setMediaItem(mediaItem) - ///exoPlayer.getMediaItemAt(5) - exoPlayer.prepare() - exoPlayer.play() - - view.findViewById(R.id.title_textView).text = videoInPlayer.title - - view.findViewById(R.id.quality_select).setOnClickListener{ - //Dialog for quality selection - val builder: AlertDialog.Builder? = activity?.let { - AlertDialog.Builder(it) - } - builder!!.setTitle(R.string.choose_quality_dialog) - .setItems(videosNameArray, - DialogInterface.OnClickListener { _, which -> - whichQuality = which - if(videoInPlayer.subtitles.isNotEmpty()) { - var subtitle = - mutableListOf() - subtitle?.add( - SubtitleConfiguration.Builder(videoInPlayer.subtitles[0].url.toUri()) - .setMimeType(videoInPlayer.subtitles[0].mimeType) // The correct MIME type (required). - .setLanguage(videoInPlayer.subtitles[0].code) // The subtitle language (optional). - .build() - ) - } - if(which==0){ - val mediaItem: MediaItem = MediaItem.Builder() - .setUri(videoInPlayer.hls) - .setSubtitleConfigurations(subtitle) - .build() - exoPlayer.setMediaItem(mediaItem) - }else{ - val dataSourceFactory: DataSource.Factory = - DefaultHttpDataSource.Factory() - val videoItem: MediaItem = MediaItem.Builder() - .setUri(videoInPlayer.videoStreams[which-1].url) - .setSubtitleConfigurations(subtitle) - .build() - val videoSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory) - .createMediaSource(videoItem) - var audioSource: MediaSource = DefaultMediaSourceFactory(dataSourceFactory) - .createMediaSource(fromUri(videoInPlayer.audioStreams[0].url)) - if (videoInPlayer.videoStreams[which-1].quality=="720p" || videoInPlayer.videoStreams[which-1].quality=="1080p" || videoInPlayer.videoStreams[which-1].quality=="480p" ){ - audioSource = ProgressiveMediaSource.Factory(dataSourceFactory) - .createMediaSource(fromUri(videoInPlayer.audioStreams[getMostBitRate(videoInPlayer.audioStreams)].url)) - //println("fuckkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkitttttttttttttttttttttt") - } - val mergeSource: MediaSource = MergingMediaSource(videoSource,audioSource) - exoPlayer.setMediaSource(mergeSource) - } - view.findViewById(R.id.quality_text).text=videosNameArray[which] - }) - val dialog: AlertDialog? = builder?.create() - dialog?.show() - } - //Listener for play and pause icon change - exoPlayer!!.addListener(object : com.google.android.exoplayer2.Player.Listener { - override fun onPlayerStateChanged(playWhenReady: Boolean,playbackState: Int) { - if (playWhenReady && playbackState == Player.STATE_READY) { - // media actually playing - view.findViewById(R.id.play_imageView).setImageResource(R.drawable.ic_pause) - } else if (playWhenReady) { - // might be idle (plays after prepare()), - // buffering (plays when data available) - // or ended (plays when seek away from end) - view.findViewById(R.id.play_imageView).setImageResource(R.drawable.ic_play) - } else { - // player paused in any state - view.findViewById(R.id.play_imageView).setImageResource(R.drawable.ic_play) - } - } - }) - } - } - - - } - })*/ - lifecycleScope.launchWhenCreated { val response = try { RetrofitInstance.api.getStreams(videoId!!) diff --git a/app/src/main/java/xyz/btcland/libretube/SearchFragment.kt b/app/src/main/java/xyz/btcland/libretube/SearchFragment.kt new file mode 100644 index 000000000..14ad7606f --- /dev/null +++ b/app/src/main/java/xyz/btcland/libretube/SearchFragment.kt @@ -0,0 +1,123 @@ +package xyz.btcland.libretube + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.AutoCompleteTextView +import androidx.lifecycle.lifecycleScope +import retrofit2.HttpException +import java.io.IOException + +// 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 [SearchFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class SearchFragment : Fragment() { + // TODO: Rename and change types of parameters + private var param1: String? = null + 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 onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.fragment_search, container, false) + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val autoTextView = view.findViewById(R.id.autoCompleteTextView) + autoTextView.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + if(s!! != ""){ + println(s.toString()) + fetchSuggestions(s.toString(), autoTextView) + } + + } + + override fun afterTextChanged(s: Editable?) { + + } + + }) + + + + + } + + private fun fetchSuggestions(query: String, autoTextView: AutoCompleteTextView){ + lifecycleScope.launchWhenCreated { + val response = try { + RetrofitInstance.api.getSuggestions(query) + } catch (e: IOException) { + println(e) + Log.e(TAG, "IOException, you might not have internet connection") + return@launchWhenCreated + } catch (e: HttpException) { + Log.e(TAG, "HttpException, unexpected response") + return@launchWhenCreated + } + val adapter = ArrayAdapter(context!!, android.R.layout.simple_list_item_1, response) + autoTextView.setAdapter(adapter) + } + } + + 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 SearchFragment. + */ + // TODO: Rename and change types and number of parameters + @JvmStatic + fun newInstance(param1: String, param2: String) = + SearchFragment().apply { + arguments = Bundle().apply { + putString(ARG_PARAM1, param1) + putString(ARG_PARAM2, param2) + } + } + } + + private fun Fragment?.runOnUiThread(action: () -> Unit) { + this ?: return + if (!isAdded) return // Fragment not attached to an Activity + activity?.runOnUiThread(action) + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/btcland/libretube/obj/Playlist.java b/app/src/main/java/xyz/btcland/libretube/obj/Playlist.java deleted file mode 100644 index a4045ecf0..000000000 --- a/app/src/main/java/xyz/btcland/libretube/obj/Playlist.java +++ /dev/null @@ -1,23 +0,0 @@ -package xyz.btcland.libretube.obj; - -import java.util.List; - -public class Playlist { - - public String name, thumbnailUrl, bannerUrl, nextpage, uploader, uploaderUrl, uploaderAvatar; - public int videos; - public List relatedStreams; - - public Playlist(String name, String thumbnailUrl, String bannerUrl, String nextpage, String uploader, - String uploaderUrl, String uploaderAvatar, int videos, List relatedStreams) { - this.name = name; - this.thumbnailUrl = thumbnailUrl; - this.bannerUrl = bannerUrl; - this.nextpage = nextpage; - this.videos = videos; - this.uploader = uploader; - this.uploaderUrl = uploaderUrl; - this.uploaderAvatar = uploaderAvatar; - this.relatedStreams = relatedStreams; - } -} diff --git a/app/src/main/java/xyz/btcland/libretube/obj/Playlist.kt b/app/src/main/java/xyz/btcland/libretube/obj/Playlist.kt new file mode 100644 index 000000000..35c7503c5 --- /dev/null +++ b/app/src/main/java/xyz/btcland/libretube/obj/Playlist.kt @@ -0,0 +1,13 @@ +package xyz.btcland.libretube.obj + +data class Playlist( + var name: String? = null, + var thumbnailUrl: String? = null, + var bannerUrl: String? = null, + var nextpage: String? = null, + var uploader: String? = null, + var uploaderUrl: String? = null, + var uploaderAvatar: String? = null, + var videos: Int = 0, + var relatedStreams: List? = null, +) diff --git a/app/src/main/java/xyz/btcland/libretube/obj/StreamItem.kt b/app/src/main/java/xyz/btcland/libretube/obj/StreamItem.kt index 57ff146df..245fb336f 100644 --- a/app/src/main/java/xyz/btcland/libretube/obj/StreamItem.kt +++ b/app/src/main/java/xyz/btcland/libretube/obj/StreamItem.kt @@ -1,5 +1,15 @@ package xyz.btcland.libretube.obj +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo +import xyz.btcland.libretube.obj.search.SearchChannel +import xyz.btcland.libretube.obj.search.SearchPlaylist + +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(value =[ + JsonSubTypes.Type(SearchChannel::class), + JsonSubTypes.Type(SearchPlaylist::class) +]) data class StreamItem( var url: String?, var title: String?, diff --git a/app/src/main/java/xyz/btcland/libretube/obj/search/SearchChannel.kt b/app/src/main/java/xyz/btcland/libretube/obj/search/SearchChannel.kt new file mode 100644 index 000000000..508b762dd --- /dev/null +++ b/app/src/main/java/xyz/btcland/libretube/obj/search/SearchChannel.kt @@ -0,0 +1,11 @@ +package xyz.btcland.libretube.obj.search + +data class SearchChannel( + var name: String? = null, + var thumbnail: String? = null, + var url: String? = null, + var description: String? = null, + var subscribers: Long? = -1, + var videos: Long? = -1, + var verified: Boolean? = null +) diff --git a/app/src/main/java/xyz/btcland/libretube/obj/search/SearchPlaylist.kt b/app/src/main/java/xyz/btcland/libretube/obj/search/SearchPlaylist.kt new file mode 100644 index 000000000..7ad42fd20 --- /dev/null +++ b/app/src/main/java/xyz/btcland/libretube/obj/search/SearchPlaylist.kt @@ -0,0 +1,10 @@ +package xyz.btcland.libretube.obj.search + +data class SearchPlaylist( + var name: String? = null, + var thumbnail: String? = null, + var url: String? = null, + var uploaderName: String? =null, + var videos: Long = -1 + +) diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 000000000..e2dd96c6d --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_search2.xml b/app/src/main/res/drawable/ic_search2.xml new file mode 100644 index 000000000..d401039d2 --- /dev/null +++ b/app/src/main/res/drawable/ic_search2.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 000000000..b240b8300 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 000000000..ffe829031 --- /dev/null +++ b/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,45 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/action_bar.xml b/app/src/main/res/menu/action_bar.xml new file mode 100644 index 000000000..e574e70e7 --- /dev/null +++ b/app/src/main/res/menu/action_bar.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_menu.xml b/app/src/main/res/menu/bottom_menu.xml index d0f18f432..d55a8d6ef 100644 --- a/app/src/main/res/menu/bottom_menu.xml +++ b/app/src/main/res/menu/bottom_menu.xml @@ -4,5 +4,6 @@ + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav.xml b/app/src/main/res/navigation/nav.xml index 3e5346f8e..fbd19bd1e 100644 --- a/app/src/main/res/navigation/nav.xml +++ b/app/src/main/res/navigation/nav.xml @@ -9,15 +9,32 @@ android:id="@+id/home2" android:name="xyz.btcland.libretube.Home" android:label="fragment_home" - tools:layout="@layout/fragment_home" /> + tools:layout="@layout/fragment_home" > + + + tools:layout="@layout/fragment_subscriptions" > + + + tools:layout="@layout/fragment_library" > + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d36fd8e4b..853721ee9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,7 @@ LibreTube - + Hello blank fragment Choose Quality: + Search \ No newline at end of file