Skip to content

Commit b1646ff

Browse files
author
Rohit Singh
committed
- Jsoup and Coil libs added in build.gradle
- linkpreview package created for loose coupling
1 parent a04dd61 commit b1646ff

File tree

11 files changed

+603
-1
lines changed

11 files changed

+603
-1
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
# LinkParser
1+
# Android Link Preview
2+
3+
An Android Project with demo application, to fetch meta-data from url, like facebook, youtube and other websites.
4+
5+
##Usage
6+
7+
Request metadata for url, it will execute in background thread, you will get an instance from LinkPreview
8+

app/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ android {
3030
kotlinOptions {
3131
jvmTarget = '1.8'
3232
}
33+
buildFeatures {
34+
// for view binding:
35+
viewBinding = true
36+
}
37+
3338
}
3439

3540
dependencies {
@@ -41,4 +46,9 @@ dependencies {
4146
testImplementation 'junit:junit:4.13.2'
4247
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
4348
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
49+
//getting meta tag from links
50+
implementation 'org.jsoup:jsoup:1.15.2'
51+
//coil image loading libs
52+
implementation("io.coil-kt:coil:1.4.0")
53+
4454
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.linkpreview.none.linkpreview
2+
3+
/**
4+
* Different types of articles that can be handled
5+
*/
6+
enum class ImageType { DEFAULT, YOUTUBE, NONE }
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.linkpreview.none.linkpreview
2+
3+
import androidx.annotation.Keep
4+
5+
@Keep
6+
data class PreviewData(val siteName : String,val title: String, val imageUrl: String, val baseUrl: String) {
7+
8+
fun isEmpty(): Boolean = title.isEmpty() && baseUrl.isEmpty()
9+
10+
fun isNotEmpty(): Boolean = !isEmpty()
11+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package com.linkpreview.none.linkpreview.extension
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.net.Uri
6+
import android.util.Log
7+
import android.view.View
8+
import com.linkpreview.none.linkpreview.PreviewData
9+
import com.linkpreview.none.linkpreview.listener.LinkListener
10+
import com.linkpreview.none.linkpreview.view.LinkPreview
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.launch
13+
import kotlinx.coroutines.withContext
14+
import org.jsoup.Jsoup
15+
import org.jsoup.nodes.Document
16+
import org.jsoup.nodes.Element
17+
import java.net.URI
18+
19+
@Suppress("BlockingMethodInNonBlockingContext")
20+
suspend fun LinkPreview.loadPreviewData(
21+
link: String,
22+
key: Int,
23+
listener: LinkListener?
24+
) = withContext(Dispatchers.Default) {
25+
try {
26+
val result = try {
27+
val connection = Jsoup.connect(link).referrer("http://www.google.com")
28+
29+
val doc: Document = connection.get()
30+
val imageElements = doc.select("meta[property=og:image]")
31+
32+
//Get description from document object.
33+
val dataLink : Element? = doc.select("a").first()
34+
35+
val siteName: String =getHostName(link).toString()
36+
37+
38+
if (imageElements.size > 0) {
39+
var it = 0
40+
var chosen: String? = ""
41+
42+
while ((chosen == null || chosen.isEmpty()) && it < imageElements.size) {
43+
chosen = imageElements[it].attr("content")
44+
it += 1
45+
}
46+
47+
PreviewData(siteName,doc.title(), chosen ?: "", link)
48+
} else {
49+
launch(Dispatchers.Main) { listener?.onError() }
50+
PreviewData("","", "", "")
51+
}
52+
} catch (e: IndexOutOfBoundsException) {
53+
e.printStackTrace()
54+
launch(Dispatchers.Main) { listener?.onError() }
55+
PreviewData("","", "", "")
56+
} catch (e: Exception) {
57+
e.printStackTrace()
58+
launch(Dispatchers.Main) { listener?.onError() }
59+
PreviewData("","", "", "")
60+
}
61+
62+
launch(Dispatchers.Main) {
63+
try {
64+
if (result.isNotEmpty()) {
65+
setPreviewData(result)
66+
listener?.onSuccess(result)
67+
} else {
68+
Log.d("Article Request", "Image url is empty")
69+
visibility = View.GONE
70+
listener?.onError()
71+
}
72+
} catch (e: Exception) {
73+
e.printStackTrace()
74+
listener?.onError()
75+
} catch (e: IllegalArgumentException) {
76+
e.printStackTrace()
77+
listener?.onError()
78+
}
79+
}
80+
} catch (e: Exception) {
81+
e.printStackTrace()
82+
}
83+
}
84+
85+
86+
87+
fun Context.launchUrlWithCustomTab(uri: Uri) {
88+
try{
89+
val browserIntent = Intent(Intent.ACTION_VIEW,uri)
90+
startActivity(browserIntent)
91+
}catch (e : Exception){
92+
e.printStackTrace()
93+
}
94+
}
95+
96+
@Suppress("BlockingMethodInNonBlockingContext")
97+
suspend fun loadPreviewData(
98+
link: String,
99+
listener: LinkListener?
100+
) = withContext(Dispatchers.Default) {
101+
try {
102+
val result = try {
103+
val connection = Jsoup.connect(link).referrer("http://www.google.com")
104+
105+
val doc: Document = connection.get()
106+
val imageElements = doc.select("meta[property=og:image]")
107+
108+
//Get description from document object.
109+
val dataLink : Element? = doc.select("a").first()
110+
111+
val siteName: String = getHostName(link).toString()
112+
113+
114+
if (imageElements.size > 0) {
115+
var it = 0
116+
var chosen: String? = ""
117+
118+
while ((chosen == null || chosen.isEmpty()) && it < imageElements.size) {
119+
chosen = imageElements[it].attr("content")
120+
it += 1
121+
}
122+
123+
PreviewData(siteName,doc.title(), chosen ?: "", link)
124+
} else {
125+
launch(Dispatchers.Main) { listener?.onError() }
126+
PreviewData("","", "", "")
127+
}
128+
} catch (e: IndexOutOfBoundsException) {
129+
e.printStackTrace()
130+
launch(Dispatchers.Main) { listener?.onError() }
131+
PreviewData("","", "", "")
132+
} catch (e: Exception) {
133+
e.printStackTrace()
134+
launch(Dispatchers.Main) { listener?.onError() }
135+
PreviewData("","", "", "")
136+
}
137+
138+
launch(Dispatchers.Main) {
139+
try {
140+
if (result.isNotEmpty()) {
141+
listener?.onSuccess(result)
142+
} else {
143+
listener?.onError()
144+
}
145+
} catch (e: Exception) {
146+
e.printStackTrace()
147+
listener?.onError()
148+
} catch (e: IllegalArgumentException) {
149+
e.printStackTrace()
150+
listener?.onError()
151+
}
152+
}
153+
} catch (e: Exception) {
154+
listener?.onError()
155+
e.printStackTrace()
156+
}
157+
}
158+
159+
/*
160+
* get hostName */
161+
fun getHostName(baseUrl: String): CharSequence {
162+
163+
val uri = URI(baseUrl)
164+
val hostname: String = uri.host
165+
return if (hostname.startsWith("www.")) hostname.substring(4) else hostname
166+
167+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.linkpreview.none.linkpreview.extension
2+
3+
/**
4+
* Checks whether a string is a url by using Regex
5+
*/
6+
fun String.isUrl(): Boolean = this.matches(Regex(REGEX_URL))
7+
8+
/**
9+
* Parses the link from the full string
10+
*/
11+
fun String.parseUrl(): String = REGEX_URL.toRegex().find(this)?.value ?: ""
12+
13+
/**
14+
* Regex pattern that matches to standard urls
15+
*/
16+
private const val REGEX_URL = "https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_+.~#?&/=]*)"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.linkpreview.none.linkpreview.helper
2+
3+
import android.content.Context
4+
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.withContext
6+
import org.jsoup.Jsoup
7+
import org.jsoup.nodes.Document
8+
9+
object LinkPreviewNetwork {
10+
11+
/**
12+
*
13+
*/
14+
suspend fun loadImage(context: Context, link: String): String? {
15+
return try {
16+
withContext(Dispatchers.Default) {
17+
val key = link.hashCode()
18+
19+
try {
20+
val connection = Jsoup.connect(link)
21+
val doc: Document = connection.get()
22+
val imageElements = doc.select("meta[property=og:image]")
23+
24+
if (imageElements.size > 0) {
25+
var it = 0
26+
var chosen: String? = ""
27+
28+
while ((chosen == null || chosen.isEmpty()) && it < imageElements.size) {
29+
chosen = imageElements[it].attr("content")
30+
it += 1
31+
}
32+
33+
chosen
34+
} else {
35+
""
36+
}
37+
} catch (e: IndexOutOfBoundsException) {
38+
e.printStackTrace()
39+
""
40+
} catch (e: Exception) {
41+
e.printStackTrace()
42+
""
43+
}
44+
}
45+
} catch (e: Exception) {
46+
e.printStackTrace()
47+
""
48+
}
49+
}
50+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.linkpreview.none.linkpreview.listener
2+
3+
import com.linkpreview.none.linkpreview.PreviewData
4+
5+
interface CardListener {
6+
7+
/**
8+
* Called when there was an error in loading the image from url, recommended to hide the view
9+
*/
10+
fun onError()
11+
12+
/**
13+
* Called when image from url is loaded successfully
14+
*
15+
* @param card to url image
16+
*/
17+
fun onSuccess(card: PreviewData)
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.linkpreview.none.linkpreview.listener
2+
3+
import android.view.View
4+
import com.linkpreview.none.linkpreview.view.LinkPreview
5+
6+
interface LinkClickListener {
7+
8+
/**
9+
* Listener to provide custom preview click functionality
10+
*
11+
* @param view of type [LinkPreview] that was pressed, possibly null
12+
* @param url of the website that was parsed that should be opened
13+
*/
14+
fun onLinkClicked(view: View?, url: String)
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.linkpreview.none.linkpreview.listener
2+
3+
import com.linkpreview.none.linkpreview.PreviewData
4+
5+
interface LinkListener {
6+
7+
/**
8+
* Called when there was an error in loading the image from url, recommended to hide the view
9+
*/
10+
fun onError()
11+
12+
/**
13+
* Called when image from url is loaded successfully
14+
*
15+
* @param link to url image
16+
*/
17+
fun onSuccess(link: PreviewData)
18+
}

0 commit comments

Comments
 (0)