Skip to content

Commit c5d37eb

Browse files
committed
[feature] Support parsing the link tag as enclosure
1 parent 40adf00 commit c5d37eb

File tree

20 files changed

+483
-45
lines changed

20 files changed

+483
-45
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ android {
2020
minSdk = 24
2121
targetSdk = 34
2222
versionCode = 5
23-
versionName = "1.0-beta10"
23+
versionName = "1.0-beta11"
2424

2525
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2626

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.skyd.anivu.model.bean
2+
3+
import com.skyd.anivu.base.BaseBean
4+
5+
data class LinkEnclosureBean(
6+
val link: String,
7+
) : BaseBean
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.skyd.anivu.model.bean.settings
2+
3+
import android.graphics.drawable.Drawable
4+
import com.skyd.anivu.base.BaseBean
5+
6+
data class SettingsSwitchBean(
7+
val title: String,
8+
val description: String,
9+
val icon: Drawable,
10+
val isChecked: () -> Boolean = { false },
11+
val onCheckedChanged: ((Boolean) -> Unit)? = null,
12+
) : BaseBean
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.skyd.anivu.model.preference.rss
2+
3+
import android.content.Context
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.booleanPreferencesKey
6+
import com.skyd.anivu.base.BasePreference
7+
import com.skyd.anivu.ext.dataStore
8+
import com.skyd.anivu.ext.put
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.launch
12+
13+
object ParseLinkTagAsEnclosurePreference : BasePreference<Boolean> {
14+
private const val PARSE_LINK_TAG_AS_ENCLOSURE = "parseLinkTagAsEnclosure"
15+
override val default = true
16+
17+
val key = booleanPreferencesKey(PARSE_LINK_TAG_AS_ENCLOSURE)
18+
19+
fun put(context: Context, scope: CoroutineScope, value: Boolean) {
20+
scope.launch(Dispatchers.IO) {
21+
context.dataStore.put(key, value)
22+
}
23+
}
24+
25+
override fun fromPreferences(preferences: Preferences): Boolean = preferences[key] ?: default
26+
}

app/src/main/java/com/skyd/anivu/model/worker/download/DownloadTorrentWorker.kt

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -235,27 +235,29 @@ class DownloadTorrentWorker(context: Context, parameters: WorkerParameters) :
235235
saveDir: File,
236236
flags: torrent_flags_t = torrent_flags_t(),
237237
) {
238-
if (link.startsWith("magnet:")) {
239-
sessionManager.download(link, saveDir, flags)
240-
} else if (
241-
(link.startsWith("http:") || link.startsWith("https:")) &&
242-
link.endsWith(".torrent")
243-
) {
244-
val tempTorrentFile = File(
245-
Const.TEMP_TORRENT_DIR,
246-
link.substringAfterLast('/').toDecodedUrl().validateFileName()
247-
)
248-
hiltEntryPoint.retrofit.create(HttpService::class.java)
249-
.requestGetResponseBody(link).execute().body()!!.byteStream()
250-
.use { it.saveTo(tempTorrentFile) }
251-
sessionManager.download(
252-
TorrentInfo(tempTorrentFile), saveDir,
253-
null, null, null,
254-
flags
255-
)
256-
} else {
257-
error("Unsupported link: $link")
258-
}
238+
doIfMagnetOrTorrentLink(
239+
link = link,
240+
onMagnet = {
241+
sessionManager.download(link, saveDir, flags)
242+
},
243+
onTorrent = {
244+
val tempTorrentFile = File(
245+
Const.TEMP_TORRENT_DIR,
246+
link.substringAfterLast('/').toDecodedUrl().validateFileName()
247+
)
248+
hiltEntryPoint.retrofit.create(HttpService::class.java)
249+
.requestGetResponseBody(link).execute().body()!!.byteStream()
250+
.use { it.saveTo(tempTorrentFile) }
251+
sessionManager.download(
252+
TorrentInfo(tempTorrentFile), saveDir,
253+
null, null, null,
254+
flags
255+
)
256+
},
257+
onUnsupported = {
258+
error("Unsupported link: $link")
259+
},
260+
)
259261
}
260262

261263
override suspend fun getForegroundInfo() = createForegroundInfo()

app/src/main/java/com/skyd/anivu/model/worker/download/Util.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,24 @@ import java.io.FileOutputStream
2323
import java.io.IOException
2424

2525

26+
fun doIfMagnetOrTorrentLink(
27+
link: String,
28+
onMagnet: (String) -> Unit,
29+
onTorrent: (String) -> Unit,
30+
onUnsupported: (String) -> Unit = {},
31+
) {
32+
if (link.startsWith("magnet:")) {
33+
onMagnet(link)
34+
} else if (
35+
(link.startsWith("http:") || link.startsWith("https:")) &&
36+
link.endsWith(".torrent")
37+
) {
38+
onTorrent(link)
39+
} else {
40+
onUnsupported(link)
41+
}
42+
}
43+
2644
fun TorrentStatus.State.toDisplayString(context: Context): String {
2745
return when (this) {
2846
TorrentStatus.State.CHECKING_FILES -> context.getString(R.string.torrent_status_checking_files)

app/src/main/java/com/skyd/anivu/ui/adapter/variety/ViewHolder.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import com.skyd.anivu.databinding.ItemDownload1Binding
88
import com.skyd.anivu.databinding.ItemEnclosure1Binding
99
import com.skyd.anivu.databinding.ItemFeed1Binding
1010
import com.skyd.anivu.databinding.ItemLicense1Binding
11+
import com.skyd.anivu.databinding.ItemLinkEnclosure1Binding
1112
import com.skyd.anivu.databinding.ItemMedia1Binding
1213
import com.skyd.anivu.databinding.ItemMore1Binding
1314
import com.skyd.anivu.databinding.ItemOtherWorks1Binding
1415
import com.skyd.anivu.databinding.ItemParentDir1Binding
1516
import com.skyd.anivu.databinding.ItemSettingsBaseBinding
17+
import com.skyd.anivu.databinding.ItemSettingsSwitchBinding
1618

1719
abstract class BaseViewHolder<V : ViewBinding>(val binding: V) :
1820
RecyclerView.ViewHolder(binding.root)
@@ -27,6 +29,9 @@ class Article1ViewHolder(binding: ItemArticle1Binding) :
2729
class Enclosure1ViewHolder(binding: ItemEnclosure1Binding) :
2830
BaseViewHolder<ItemEnclosure1Binding>(binding)
2931

32+
class LinkEnclosure1ViewHolder(binding: ItemLinkEnclosure1Binding) :
33+
BaseViewHolder<ItemLinkEnclosure1Binding>(binding)
34+
3035
class Download1ViewHolder(binding: ItemDownload1Binding) :
3136
BaseViewHolder<ItemDownload1Binding>(binding)
3237

@@ -47,3 +52,6 @@ class License1ViewHolder(binding: ItemLicense1Binding) :
4752

4853
class SettingsBaseViewHolder(binding: ItemSettingsBaseBinding) :
4954
BaseViewHolder<ItemSettingsBaseBinding>(binding)
55+
56+
class SettingsSwitchViewHolder(binding: ItemSettingsSwitchBinding) :
57+
BaseViewHolder<ItemSettingsSwitchBinding>(binding)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.skyd.anivu.ui.adapter.variety.proxy
2+
3+
4+
import android.view.LayoutInflater
5+
import android.view.ViewGroup
6+
import com.skyd.anivu.databinding.ItemLinkEnclosure1Binding
7+
import com.skyd.anivu.ext.copy
8+
import com.skyd.anivu.model.bean.LinkEnclosureBean
9+
import com.skyd.anivu.ui.adapter.variety.LinkEnclosure1ViewHolder
10+
import com.skyd.anivu.ui.adapter.variety.VarietyAdapter
11+
12+
13+
class LinkEnclosure1Proxy(
14+
private val onDownload: (LinkEnclosureBean) -> Unit,
15+
) : VarietyAdapter.Proxy<LinkEnclosureBean, ItemLinkEnclosure1Binding, LinkEnclosure1ViewHolder>() {
16+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
17+
LinkEnclosure1ViewHolder(
18+
ItemLinkEnclosure1Binding
19+
.inflate(LayoutInflater.from(parent.context), parent, false),
20+
)
21+
22+
override fun onBindViewHolder(
23+
holder: LinkEnclosure1ViewHolder,
24+
data: LinkEnclosureBean,
25+
index: Int,
26+
action: ((Any?) -> Unit)?
27+
) {
28+
val context = holder.itemView.context
29+
holder.binding.apply {
30+
tvEnclosure1Url.text = data.link
31+
tvEnclosure1Url.setOnClickListener {
32+
data.link.copy(context)
33+
}
34+
btnEnclosure1Download.setOnClickListener {
35+
onDownload(data)
36+
}
37+
}
38+
}
39+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.skyd.anivu.ui.adapter.variety.proxy.settings
2+
3+
4+
import android.view.LayoutInflater
5+
import android.view.ViewGroup
6+
import com.skyd.anivu.databinding.ItemSettingsSwitchBinding
7+
import com.skyd.anivu.model.bean.settings.SettingsSwitchBean
8+
import com.skyd.anivu.ui.adapter.variety.SettingsSwitchViewHolder
9+
import com.skyd.anivu.ui.adapter.variety.VarietyAdapter
10+
11+
12+
class SettingsSwitchProxy(
13+
private val adapter: VarietyAdapter,
14+
) : VarietyAdapter.Proxy<SettingsSwitchBean, ItemSettingsSwitchBinding, SettingsSwitchViewHolder>() {
15+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsSwitchViewHolder {
16+
val holder = SettingsSwitchViewHolder(
17+
ItemSettingsSwitchBinding
18+
.inflate(LayoutInflater.from(parent.context), parent, false),
19+
)
20+
21+
holder.itemView.setOnClickListener {
22+
val data = adapter.dataList.getOrNull(holder.bindingAdapterPosition)
23+
if (data !is SettingsSwitchBean) return@setOnClickListener
24+
holder.binding.apply {
25+
switchSettingsSwitch.isChecked = !switchSettingsSwitch.isChecked
26+
data.onCheckedChanged?.invoke(switchSettingsSwitch.isChecked)
27+
}
28+
}
29+
return holder
30+
}
31+
32+
override fun onBindViewHolder(
33+
holder: SettingsSwitchViewHolder,
34+
data: SettingsSwitchBean,
35+
index: Int,
36+
action: ((Any?) -> Unit)?
37+
) {
38+
holder.binding.apply {
39+
tvSettingsSwitchTitle.text = data.title
40+
tvSettingsSwitchDesc.text = data.description
41+
ivSettingsSwitchIcon.setImageDrawable(data.icon)
42+
switchSettingsSwitch.isChecked = data.isChecked()
43+
}
44+
}
45+
}

app/src/main/java/com/skyd/anivu/ui/fragment/read/EnclosureBottomSheet.kt

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import com.skyd.anivu.R
1111
import com.skyd.anivu.base.BaseBottomSheetDialogFragment
1212
import com.skyd.anivu.databinding.BottomSheetEnclosureBinding
1313
import com.skyd.anivu.model.bean.EnclosureBean
14+
import com.skyd.anivu.model.bean.LinkEnclosureBean
1415
import com.skyd.anivu.model.worker.download.DownloadTorrentWorker
1516
import com.skyd.anivu.ui.adapter.variety.AniSpanSize
1617
import com.skyd.anivu.ui.adapter.variety.VarietyAdapter
1718
import com.skyd.anivu.ui.adapter.variety.proxy.Enclosure1Proxy
19+
import com.skyd.anivu.ui.adapter.variety.proxy.LinkEnclosure1Proxy
1820

1921
class EnclosureBottomSheet : BaseBottomSheetDialogFragment<BottomSheetEnclosureBinding>() {
2022
companion object {
@@ -26,38 +28,56 @@ class EnclosureBottomSheet : BaseBottomSheetDialogFragment<BottomSheetEnclosureB
2628
) { resultMap ->
2729
if (!resultMap.containsValue(false)) {
2830
waitingDownloadList.removeIf {
31+
val url = when (it) {
32+
is EnclosureBean -> it.url
33+
is LinkEnclosureBean -> it.link
34+
else -> return@removeIf true
35+
}
36+
2937
DownloadTorrentWorker.startWorker(
3038
context = requireContext(),
31-
torrentLink = it.url
39+
torrentLink = url
3240
)
3341
true
3442
}
3543
}
3644
}
3745

38-
private val waitingDownloadList = mutableListOf<EnclosureBean>()
46+
private val waitingDownloadList = mutableListOf<Any>()
3947

40-
private val adapter = VarietyAdapter(
41-
mutableListOf(Enclosure1Proxy(onDownload = {
42-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
43-
waitingDownloadList += it
44-
registerPostNotificationPermission.launch(
45-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
46-
arrayOf(
47-
android.Manifest.permission.POST_NOTIFICATIONS,
48-
android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
49-
)
50-
} else arrayOf(android.Manifest.permission.POST_NOTIFICATIONS)
51-
)
52-
} else {
48+
private val onDownload: (Any) -> Unit = {
49+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
50+
waitingDownloadList += it
51+
registerPostNotificationPermission.launch(
52+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
53+
arrayOf(
54+
android.Manifest.permission.POST_NOTIFICATIONS,
55+
android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
56+
)
57+
} else arrayOf(android.Manifest.permission.POST_NOTIFICATIONS)
58+
)
59+
} else {
60+
val url = when (it) {
61+
is EnclosureBean -> it.url
62+
is LinkEnclosureBean -> it.link
63+
else -> null
64+
}
65+
if (!url.isNullOrBlank()) {
5366
DownloadTorrentWorker.startWorker(
54-
context = requireContext(), torrentLink = it.url
67+
context = requireContext(), torrentLink = url
5568
)
5669
}
57-
}))
70+
}
71+
}
72+
73+
private val adapter = VarietyAdapter(
74+
mutableListOf(
75+
Enclosure1Proxy(onDownload = onDownload),
76+
LinkEnclosure1Proxy(onDownload = onDownload),
77+
)
5878
)
5979

60-
fun updateData(dataList: List<EnclosureBean>) {
80+
fun updateData(dataList: List<Any>) {
6181
adapter.dataList = dataList
6282
}
6383

app/src/main/java/com/skyd/anivu/ui/fragment/read/ReadFragment.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@ import com.skyd.anivu.ext.addFabBottomPaddingHook
1515
import com.skyd.anivu.ext.addInsetsByMargin
1616
import com.skyd.anivu.ext.addInsetsByPadding
1717
import com.skyd.anivu.ext.collectIn
18+
import com.skyd.anivu.ext.dataStore
19+
import com.skyd.anivu.ext.getOrDefault
1820
import com.skyd.anivu.ext.ifNullOfBlank
1921
import com.skyd.anivu.ext.openBrowser
2022
import com.skyd.anivu.ext.popBackStackWithLifecycle
2123
import com.skyd.anivu.ext.startWith
2224
import com.skyd.anivu.ext.toHtml
25+
import com.skyd.anivu.model.bean.LinkEnclosureBean
26+
import com.skyd.anivu.model.preference.rss.ParseLinkTagAsEnclosurePreference
27+
import com.skyd.anivu.model.worker.download.doIfMagnetOrTorrentLink
2328
import com.skyd.anivu.util.html.ImageGetter
2429
import dagger.hilt.android.AndroidEntryPoint
2530
import kotlinx.coroutines.channels.Channel
@@ -80,7 +85,17 @@ class ReadFragment : BaseFragment<FragmentReadBinding>() {
8085
)
8186
}
8287

83-
enclosureBottomSheet?.updateData(article.enclosures)
88+
val dataList: MutableList<Any> = article.enclosures.toMutableList()
89+
if (requireContext().dataStore.getOrDefault(ParseLinkTagAsEnclosurePreference)) {
90+
article.article.link?.let { link ->
91+
doIfMagnetOrTorrentLink(
92+
link = link,
93+
onMagnet = { dataList += LinkEnclosureBean(link = link) },
94+
onTorrent = { dataList += LinkEnclosureBean(link = link) },
95+
)
96+
}
97+
}
98+
enclosureBottomSheet?.updateData(dataList)
8499
}
85100
}
86101
}
@@ -131,7 +146,17 @@ class ReadFragment : BaseFragment<FragmentReadBinding>() {
131146
)
132147
val articleState = feedViewModel.viewState.value.articleState
133148
if (articleState is ArticleState.Success) {
134-
enclosureBottomSheet?.updateData(articleState.article.enclosures)
149+
val dataList: MutableList<Any> = articleState.article.enclosures.toMutableList()
150+
if (requireContext().dataStore.getOrDefault(ParseLinkTagAsEnclosurePreference)) {
151+
articleState.article.article.link?.let { link ->
152+
doIfMagnetOrTorrentLink(
153+
link = link,
154+
onMagnet = { dataList += LinkEnclosureBean(link = link) },
155+
onTorrent = { dataList += LinkEnclosureBean(link = link) },
156+
)
157+
}
158+
}
159+
enclosureBottomSheet?.updateData(dataList)
135160
}
136161
}
137162
}

0 commit comments

Comments
 (0)