diff --git a/src/vi/mangaxy/AndroidManifest.xml b/src/vi/mangaxy/AndroidManifest.xml
new file mode 100644
index 000000000..b4571bfa8
--- /dev/null
+++ b/src/vi/mangaxy/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/vi/mangaxy/build.gradle b/src/vi/mangaxy/build.gradle
new file mode 100644
index 000000000..2f9aaacb2
--- /dev/null
+++ b/src/vi/mangaxy/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'MangaXY'
+ pkgNameSuffix = 'vi.MangaXY'
+ extClass = '.MangaXY'
+ extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
\ No newline at end of file
diff --git a/src/vi/mangaxy/res/mipmap-hdpi/ic_launcher.png b/src/vi/mangaxy/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..f9e48c8f3
Binary files /dev/null and b/src/vi/mangaxy/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/vi/mangaxy/res/mipmap-mdpi/ic_launcher.png b/src/vi/mangaxy/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..eea7e8394
Binary files /dev/null and b/src/vi/mangaxy/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/vi/mangaxy/res/mipmap-xhdpi/ic_launcher.png b/src/vi/mangaxy/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..2ced1b6cb
Binary files /dev/null and b/src/vi/mangaxy/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/vi/mangaxy/res/mipmap-xxhdpi/ic_launcher.png b/src/vi/mangaxy/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..3377f2e97
Binary files /dev/null and b/src/vi/mangaxy/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/vi/mangaxy/res/mipmap-xxxhdpi/ic_launcher.png b/src/vi/mangaxy/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..2e1bc0c5d
Binary files /dev/null and b/src/vi/mangaxy/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/vi/mangaxy/res/web_hi_res_512.png b/src/vi/mangaxy/res/web_hi_res_512.png
new file mode 100644
index 000000000..bf077cd58
Binary files /dev/null and b/src/vi/mangaxy/res/web_hi_res_512.png differ
diff --git a/src/vi/mangaxy/src/eu/kanade/tachiyomi/extension/vi/MangaXY/MangaXY.kt b/src/vi/mangaxy/src/eu/kanade/tachiyomi/extension/vi/MangaXY/MangaXY.kt
new file mode 100644
index 000000000..51cdb8479
--- /dev/null
+++ b/src/vi/mangaxy/src/eu/kanade/tachiyomi/extension/vi/MangaXY/MangaXY.kt
@@ -0,0 +1,342 @@
+package eu.kanade.tachiyomi.extension.vi.MangaXY
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.source.model.Filter
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.OkHttpClient
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+class MangaXY : ParsedHttpSource() {
+
+ override val name = "MangaXY"
+
+ override val baseUrl = "https://mangaxy.com"
+
+ override val lang = "vi"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ override fun headersBuilder() = Headers.Builder()
+ .add("Referer", "$baseUrl/")
+
+ private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).apply {
+ timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh")
+ }
+
+ override fun popularMangaRequest(page: Int) = GET(
+ "$baseUrl/search.php".toHttpUrl().newBuilder()
+ .addQueryParameter("act", "search")
+ .addQueryParameter("sort", "xem")
+ .addQueryParameter("view", "thumb")
+ .addQueryParameter("page", page.toString())
+ .toString(),
+ headers
+ )
+ override fun popularMangaSelector() = ".container > .row > div.col-12.col-lg-9 > #tblChap > .thumb"
+
+ override fun latestUpdatesSelector() = popularMangaSelector()
+
+ override fun latestUpdatesRequest(page: Int) = GET(
+ "$baseUrl/search.php".toHttpUrl().newBuilder()
+ .addQueryParameter("act", "search")
+ .addQueryParameter("sort", "chap")
+ .addQueryParameter("view", "thumb")
+ .addQueryParameter("page", page.toString())
+ .toString(),
+ headers
+ )
+
+ override fun popularMangaFromElement(element: Element): SManga {
+ val manga = SManga.create()
+ element.select("a.name").first().let {
+ manga.setUrlWithoutDomain(it.attr("href"))
+ manga.title = it.text().trim()
+ }
+ manga.thumbnail_url = element.select(".item img")
+ .first()
+ .attr("style")
+ .substringAfter("url('")
+ .substringBefore("')")
+ .replace("//", "https:")
+ .replace("http:", "")
+ return manga
+ }
+
+ override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
+
+ override fun popularMangaNextPageSelector(): String = "div#tblChap p.page a:contains(Cuối)"
+
+ override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
+
+ open class UriPartFilter(displayName: String, private val vals: Array>, state: Int = 0) :
+ Filter.Select(displayName, vals.map { it.first }.toTypedArray(), state) {
+ fun toUriPart() = vals[state].second
+ }
+ private class SortByFilter : UriPartFilter(
+ "Sắp xếp theo",
+ arrayOf(
+ Pair("Chap mới", "chap"),
+ Pair("Truyện mới", "truyen"),
+ Pair("Xem nhiều", "xem"),
+ Pair("Theo ABC", "ten"),
+ Pair("Số Chương", "sochap"),
+ ),
+ 2
+ )
+ private class SearchTypeFilter : UriPartFilter(
+ "Kiểu tìm",
+ arrayOf(
+ Pair("AND/và", "and"),
+ Pair("OR/hoặc", "or"),
+ )
+ )
+ private class ForFilter : UriPartFilter(
+ "Dành cho",
+ arrayOf(
+ Pair("Bất kì", ""),
+ Pair("Con gái", "gai"),
+ Pair("Con trai", "trai"),
+ Pair("Con nít", "nit"),
+ )
+ )
+ private class AgeFilter : UriPartFilter(
+ "Bất kỳ",
+ arrayOf(
+ Pair("Bất kì", ""),
+ Pair("= 13", "13"),
+ Pair("= 14", "14"),
+ Pair("= 15", "15"),
+ Pair("= 16", "16"),
+ Pair("= 17", "17"),
+ Pair("= 18", "18"),
+ )
+ )
+ private class StatusFilter : UriPartFilter(
+ "Tình trạng",
+ arrayOf(
+ Pair("Bất kì", ""),
+ Pair("Đang dịch", "Ongoing"),
+ Pair("Hoàn thành", "Complete"),
+ Pair("Tạm ngưng", "Drop"),
+ )
+ )
+ private class OriginFilter : UriPartFilter(
+ "Quốc gia",
+ arrayOf(
+ Pair("Bất kì", ""),
+ Pair("Nhật Bản", "nhat"),
+ Pair("Trung Quốc", "trung"),
+ Pair("Hàn Quốc", "han"),
+ Pair("Việt Nam", "vietnam"),
+ )
+ )
+ private class ReadingModeFilter : UriPartFilter(
+ "Kiểu đọc",
+ arrayOf(
+ Pair("Bất kì", ""),
+ Pair("Chưa xác định", "chưa xác định"),
+ Pair("Phải qua trái", "xem từ phải qua trái"),
+ Pair("Trái qua phải", "xem từ trái qua phải"),
+ )
+ )
+ private class YearFilter : Filter.Text("Năm phát hành")
+ private class UserFilter : Filter.Text("Đăng bởi thành viên")
+ private class AuthorFilter : Filter.Text("Tên tác giả")
+ private class SourceFilter : Filter.Text("Nguồn/Nhóm dịch")
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET(
+ "$baseUrl/search.php".toHttpUrl().newBuilder().apply {
+ addQueryParameter("act", "timnangcao")
+ addQueryParameter("view", "thumb")
+ addQueryParameter("page", page.toString())
+
+ if (query.isNotEmpty()) {
+ addQueryParameter("q", query)
+ }
+
+ (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
+ when (filter) {
+ is SortByFilter -> addQueryParameter("sort", filter.toUriPart())
+ is SearchTypeFilter -> addQueryParameter("andor", filter.toUriPart())
+ is ForFilter -> if (filter.state != 0) {
+ addQueryParameter("danhcho", filter.toUriPart())
+ }
+ is AgeFilter -> if (filter.state != 0) {
+ addQueryParameter("DoTuoi", filter.toUriPart())
+ }
+ is StatusFilter -> if (filter.state != 0) {
+ addQueryParameter("TinhTrang", filter.toUriPart())
+ }
+ is OriginFilter -> if (filter.state != 0) {
+ addQueryParameter("quocgia", filter.toUriPart())
+ }
+ is ReadingModeFilter -> if (filter.state != 0) {
+ addQueryParameter("KieuDoc", filter.toUriPart())
+ }
+ is YearFilter -> if (filter.state.isNotEmpty()) {
+ addQueryParameter("NamPhaHanh", filter.state)
+ }
+ is UserFilter -> if (filter.state.isNotEmpty()) {
+ addQueryParameter("u", filter.state)
+ }
+ is AuthorFilter -> if (filter.state.isNotEmpty()) {
+ addQueryParameter("TacGia", filter.state)
+ }
+ is SourceFilter -> if (filter.state.isNotEmpty()) {
+ addQueryParameter("Nguon", filter.state)
+ }
+ is GenreList -> {
+ addQueryParameter(
+ "baogom",
+ filter.state
+ .filter { it.state == Filter.TriState.STATE_INCLUDE }
+ .joinToString(",") { it.id }
+ )
+ addQueryParameter(
+ "khonggom",
+ filter.state
+ .filter { it.state == Filter.TriState.STATE_EXCLUDE }
+ .joinToString(",") { it.id }
+ )
+ }
+ else -> {}
+ }
+ }
+ }.build().toString(),
+ headers
+ )
+
+ override fun searchMangaSelector() = popularMangaSelector()
+
+ override fun searchMangaFromElement(element: Element): SManga {
+ return popularMangaFromElement(element)
+ }
+
+ override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
+
+ override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+ val infoElement = document.selectFirst(".tab-content")
+ val infoTop = document.selectFirst(".detail-top-wrap")
+ title = infoTop.select("h1.comics-title").text()
+ author = infoTop.select(".created-by").joinToString { it.text() }
+ genre = infoTop.select(".top-comics-type a")
+ .filter { it.text().isNotEmpty() }
+ .joinToString(", ") { it.text() }
+ description = infoElement.select(".manga-info p").text()
+ thumbnail_url = infoTop.select(".detail-top-right img")
+ .first()
+ .attr("style")
+ .substringAfter("url('")
+ .substringBefore("')")
+ .replace("//", "https:")
+ .replace("http:", "")
+ status = when (infoElement.select(".manga-info ul li a").first().text().trim()) {
+ "Đang tiến hành" -> SManga.ONGOING
+ "Đã Hoàn Thành" -> SManga.COMPLETED
+ "Tạm ngưng" -> SManga.ON_HIATUS
+ else -> SManga.UNKNOWN
+ }
+ }
+
+ override fun chapterListSelector() = "#ChapList > .episode-item"
+ override fun chapterFromElement(element: Element) = SChapter.create().apply {
+ setUrlWithoutDomain(element.select(".episode-item").first().attr("abs:href"))
+ name = element.select(".episode-title").first().text()
+ date_upload = runCatching {
+ dateFormat.parse(element.select("div.episode-date > time").attr("datetime"))?.time
+ }.getOrNull() ?: 0L
+ }
+
+ override fun pageListParse(document: Document): List {
+ return document.select("img.img-fluid").mapIndexed { i, img ->
+ Page(i, imageUrl = img.attr("abs:src"))
+ }
+ }
+
+ override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
+
+ open class Genre(name: String, val id: String) : Filter.TriState(name)
+ private class GenreList(genres: List) : Filter.Group("Thể loại", genres)
+ override fun getFilterList() = FilterList(
+ GenreList(getGenreList()),
+ SortByFilter(),
+ SearchTypeFilter(),
+ ForFilter(),
+ AgeFilter(),
+ StatusFilter(),
+ OriginFilter(),
+ ReadingModeFilter(),
+ YearFilter(),
+ UserFilter(),
+ AuthorFilter(),
+ SourceFilter(),
+ )
+
+ private fun getGenreList() = listOf(
+ Genre("Phát Hành Tại TT8", "106"),
+ Genre("Webtoons", "112"),
+ Genre("Manga", "141"),
+ Genre("Truyện Màu", "113"),
+ Genre("Action", "52"),
+ Genre("Adult", "53"),
+ Genre("Adventure", "65"),
+ Genre("Anime", "107"),
+ Genre("Biseinen", "123"),
+ Genre("Bishounen", "122"),
+ Genre("Comedy", "50"),
+ Genre("Demons", ""),
+ Genre("Doujinshi", "72"),
+ Genre("Drama", "73"),
+ Genre("Ecchi", "74"),
+ Genre("Fantasy", "75"),
+ Genre("Gender Bender", "76"),
+ Genre("Harem", "77"),
+ Genre("Hentai", ""),
+ Genre("Historical", "78"),
+ Genre("Horror", "79"),
+ Genre("Isekai", "139"),
+ Genre("Josei", "80"),
+ Genre("Live action", "81"),
+ Genre("Magic", "116"),
+ Genre("Martial Arts", "84"),
+ Genre("Mature", "85"),
+ Genre("Manhua", "82"),
+ Genre("Manhwa", "83"),
+ Genre("Mecha", "86"),
+ Genre("Mystery", "87"),
+ Genre("One-shot", "88"),
+ Genre("Oneshot", ""),
+ Genre("Other", ""),
+ Genre("Psychological", "89"),
+ Genre("Romance", "90"),
+ Genre("School Life", "91"),
+ Genre("Sci fi", "92"),
+ Genre("Seinen", "93"),
+ Genre("Shotacon", ""),
+ Genre("Shoujo", "94"),
+ Genre("Shoujo Ai", "66"),
+ Genre("Shounen", "96"),
+ Genre("Shounen Ai", "97"),
+ Genre("Slash", "121"),
+ Genre("Slice of Life", "98"),
+ Genre("Smut", "99"),
+ Genre("Sports", "101"),
+ Genre("Super power", ""),
+ Genre("Supernatural", "102"),
+ Genre("Tragedy", "104"),
+ Genre("Yaoi", "114"),
+ Genre("Yuri", "111")
+ )
+}