feat: share dialog redesign (including copy link feature)

This commit is contained in:
Bnyro 2024-07-15 13:52:38 +02:00
parent 47bae051ee
commit fadd2d31af
3 changed files with 174 additions and 75 deletions

View File

@ -122,6 +122,7 @@ object PreferenceKeys {
const val CLEAR_WATCH_HISTORY = "clear_watch_history" const val CLEAR_WATCH_HISTORY = "clear_watch_history"
const val CLEAR_WATCH_POSITIONS = "clear_watch_positions" const val CLEAR_WATCH_POSITIONS = "clear_watch_positions"
const val SHARE_WITH_TIME_CODE = "share_with_time_code" const val SHARE_WITH_TIME_CODE = "share_with_time_code"
const val SELECTED_SHARE_HOST = "selected_share_host"
const val CONFIRM_UNSUBSCRIBE = "confirm_unsubscribing" const val CONFIRM_UNSUBSCRIBE = "confirm_unsubscribing"
const val CLEAR_BOOKMARKS = "clear_bookmarks" const val CLEAR_BOOKMARKS = "clear_bookmarks"
const val MAX_CONCURRENT_DOWNLOADS = "max_parallel_downloads" const val MAX_CONCURRENT_DOWNLOADS = "max_parallel_downloads"

View File

@ -4,6 +4,7 @@ import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
@ -13,11 +14,14 @@ import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.enums.ShareObjectType import com.github.libretube.enums.ShareObjectType
import com.github.libretube.extensions.parcelable import com.github.libretube.extensions.parcelable
import com.github.libretube.extensions.serializable import com.github.libretube.extensions.serializable
import com.github.libretube.helpers.ClipboardHelper
import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.obj.ShareData import com.github.libretube.obj.ShareData
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
class ShareDialog : DialogFragment() { class ShareDialog : DialogFragment() {
private lateinit var id: String private lateinit var id: String
@ -34,72 +38,76 @@ class ShareDialog : DialogFragment() {
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
var shareOptions = arrayOf( val customInstanceUrl = getCustomInstanceFrontendUrl().toHttpUrlOrNull()
getString(R.string.piped),
getString(R.string.youtube)
)
val instanceUrl = getCustomInstanceFrontendUrl()
val shareableTitle = shareData.currentChannel val shareableTitle = shareData.currentChannel
?: shareData.currentVideo ?: shareData.currentVideo
?: shareData.currentPlaylist.orEmpty() ?: shareData.currentPlaylist.orEmpty()
// add instanceUrl option if custom instance frontend url available
if (instanceUrl.isNotEmpty()) {
shareOptions += getString(R.string.instance)
}
val binding = DialogShareBinding.inflate(layoutInflater) val binding = DialogShareBinding.inflate(layoutInflater)
return MaterialAlertDialogBuilder(requireContext()) binding.shareHostGroup.check(
.setTitle(getString(R.string.share)) when (PreferenceHelper.getInt(PreferenceKeys.SELECTED_SHARE_HOST, 0)) {
.setItems(shareOptions) { _, which -> 0 -> binding.youtube.id
val host = when (which) { 1 -> binding.piped.id
0 -> PIPED_FRONTEND_URL else -> if (customInstanceUrl != null) binding.customInstance.id else 0
1 -> YOUTUBE_FRONTEND_URL
// only available for custom instances
else -> instanceUrl
} }
var url = when { )
shareObjectType == ShareObjectType.VIDEO && host == YOUTUBE_FRONTEND_URL -> "$YOUTUBE_SHORT_URL/$id"
shareObjectType == ShareObjectType.VIDEO -> "$host/watch?v=$id" binding.shareHostGroup.setOnCheckedChangeListener { _, _ ->
shareObjectType == ShareObjectType.PLAYLIST -> "$host/playlist?list=$id" binding.linkPreview.text = generateLinkText(binding, customInstanceUrl)
else -> "$host/channel/$id" PreferenceHelper.putInt(
PreferenceKeys.SELECTED_SHARE_HOST, when {
binding.youtube.isChecked -> 0
binding.piped.isChecked -> 1
else -> 2
}
)
} }
if (shareObjectType == ShareObjectType.VIDEO && binding.timeCodeSwitch.isChecked) { if (customInstanceUrl != null) {
url += "&t=${binding.timeStamp.text}" binding.customInstance.isVisible = true
binding.customInstance.text = customInstanceUrl.host
} }
val intent = Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_TEXT, url)
.putExtra(Intent.EXTRA_SUBJECT, shareableTitle)
.setType("text/plain")
val shareIntent = Intent.createChooser(intent, getString(R.string.shareTo))
requireContext().startActivity(shareIntent)
}
.apply {
if (shareObjectType == ShareObjectType.VIDEO) { if (shareObjectType == ShareObjectType.VIDEO) {
setupTimeStampBinding(binding) binding.timeStampSwitchLayout.isVisible = true
setView(binding.root)
}
}
.show()
}
private fun setupTimeStampBinding(binding: DialogShareBinding) {
binding.timeCodeSwitch.isChecked = PreferenceHelper.getBoolean( binding.timeCodeSwitch.isChecked = PreferenceHelper.getBoolean(
PreferenceKeys.SHARE_WITH_TIME_CODE, PreferenceKeys.SHARE_WITH_TIME_CODE,
false false
) )
binding.timeCodeSwitch.setOnCheckedChangeListener { _, isChecked -> binding.timeCodeSwitch.setOnCheckedChangeListener { _, isChecked ->
binding.timeStampLayout.isVisible = isChecked binding.timeStampInputLayout.isVisible = isChecked
PreferenceHelper.putBoolean(PreferenceKeys.SHARE_WITH_TIME_CODE, isChecked) PreferenceHelper.putBoolean(PreferenceKeys.SHARE_WITH_TIME_CODE, isChecked)
binding.linkPreview.text = generateLinkText(binding, customInstanceUrl)
}
binding.timeStamp.addTextChangedListener {
binding.linkPreview.text = generateLinkText(binding, customInstanceUrl)
} }
binding.timeStamp.setText((shareData.currentPosition ?: 0L).toString()) binding.timeStamp.setText((shareData.currentPosition ?: 0L).toString())
if (binding.timeCodeSwitch.isChecked) { if (binding.timeCodeSwitch.isChecked) {
binding.timeStampLayout.isVisible = true binding.timeStampInputLayout.isVisible = true
} }
} }
binding.copyLink.setOnClickListener {
ClipboardHelper.save(requireContext(), text = binding.linkPreview.text.toString())
}
binding.linkPreview.text = generateLinkText(binding, customInstanceUrl)
return MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.share))
.setView(binding.root)
.setPositiveButton(R.string.share) { _, _ ->
val intent = Intent(Intent.ACTION_SEND)
.putExtra(Intent.EXTRA_TEXT, binding.linkPreview.text)
.putExtra(Intent.EXTRA_SUBJECT, shareableTitle)
.setType("text/plain")
val shareIntent = Intent.createChooser(intent, getString(R.string.shareTo))
requireContext().startActivity(shareIntent)
}
.show()
}
// get the frontend url if it's a custom instance // get the frontend url if it's a custom instance
private fun getCustomInstanceFrontendUrl(): String { private fun getCustomInstanceFrontendUrl(): String {
val instancePref = PreferenceHelper.getString( val instancePref = PreferenceHelper.getString(
@ -116,6 +124,27 @@ class ShareDialog : DialogFragment() {
return customInstances.firstOrNull { it.apiUrl == instancePref }?.frontendUrl.orEmpty() return customInstances.firstOrNull { it.apiUrl == instancePref }?.frontendUrl.orEmpty()
} }
private fun generateLinkText(binding: DialogShareBinding, customInstanceUrl: HttpUrl?): String {
val host = when {
binding.piped.isChecked -> PIPED_FRONTEND_URL
binding.youtube.isChecked -> YOUTUBE_FRONTEND_URL
// only available for custom instances
else -> customInstanceUrl!!.toString()
}
var url = when {
shareObjectType == ShareObjectType.VIDEO && host == YOUTUBE_FRONTEND_URL -> "$YOUTUBE_SHORT_URL/$id"
shareObjectType == ShareObjectType.VIDEO -> "$host/watch?v=$id"
shareObjectType == ShareObjectType.PLAYLIST -> "$host/playlist?list=$id"
else -> "$host/channel/$id"
}
if (shareObjectType == ShareObjectType.VIDEO && binding.timeCodeSwitch.isChecked) {
url += "&t=${binding.timeStamp.text}"
}
return url
}
companion object { companion object {
const val YOUTUBE_FRONTEND_URL = "https://www.youtube.com" const val YOUTUBE_FRONTEND_URL = "https://www.youtube.com"
const val YOUTUBE_SHORT_URL = "https://youtu.be" const val YOUTUBE_SHORT_URL = "https://youtu.be"

View File

@ -4,15 +4,51 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="25dp"> android:paddingHorizontal="20dp"
android:paddingTop="10dp">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="25dp" android:orientation="vertical"
android:orientation="horizontal"
tools:ignore="UselessParent"> tools:ignore="UselessParent">
<RadioGroup
android:id="@+id/share_host_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton
android:id="@+id/youtube"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/youtube" />
<RadioButton
android:id="@+id/piped"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/piped" />
<RadioButton
android:id="@+id/custom_instance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:text="kavin.rocks"
tools:visibility="visible" />
</RadioGroup>
<LinearLayout
android:id="@+id/time_stamp_switch_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
tools:visibility="visible">
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -29,10 +65,9 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/timeStampLayout" android:id="@+id/time_stamp_input_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="25dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:hint="@string/time_code" android:hint="@string/time_code"
android:visibility="gone"> android:visibility="gone">
@ -45,4 +80,38 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/share_link_card"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:background="?selectableItemBackground"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/copy_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="16dp">
<TextView
android:id="@+id/link_preview"
android:layout_gravity="center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
tools:text="https://youtu.be/abcdefghijgk" />
<ImageView
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_copy" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout> </LinearLayout>