This commit is contained in:
lunaticbum 2024-08-29 18:35:10 +09:00
parent 8f685f667b
commit 88dc8a3dc8
12 changed files with 982 additions and 192 deletions

View File

@ -29,6 +29,8 @@ import android.graphics.Bitmap
import android.graphics.Color
import android.net.http.SslError
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.AlarmClock
import android.telephony.TelephonyManager
import android.text.format.DateFormat
@ -74,13 +76,18 @@ import rasel.lunar.launcher.helpers.UniUtils.Companion.expandNotificationPanel
import rasel.lunar.launcher.helpers.UniUtils.Companion.lockMethod
import rasel.lunar.launcher.home.LauncherHome.Companion.refreshCalls
import rasel.lunar.launcher.home.LauncherHome.Companion.refreshSms
import rasel.lunar.launcher.home.weather.JsonParser
import rasel.lunar.launcher.home.weather.WeatherExecutor
import rasel.lunar.launcher.qaccess.QuickAccess
import rasel.lunar.launcher.settings.SettingsActivity
import rasel.lunar.launcher.todos.LinearLayoutManagerWrapper
import rasel.lunar.launcher.todos.MissedCallsAdapter
import rasel.lunar.launcher.todos.Root
import rasel.lunar.launcher.todos.RssDataItem
import rasel.lunar.launcher.todos.RssDataType
import rasel.lunar.launcher.todos.RssFeedsParser
import rasel.lunar.launcher.todos.RssItemAdapter
import rasel.lunar.launcher.todos.SmsLogsAdapter
import rasel.lunar.launcher.todos.TwoColumnBrowseResultsRenderer
import rasel.lunar.launcher.utils.BLog
import rasel.lunar.launcher.utils.MissedCallGetter
import rasel.lunar.launcher.utils.RecentSmsGetter
@ -90,7 +97,6 @@ import rasel.lunar.launcher.utils.SimpleFingerGestures
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
@ -117,6 +123,10 @@ internal class LauncherHome : Fragment() {
var smsList = arrayListOf<RecentSmsLog>()
var listTags = arrayListOf<RssTagItem>()
var listItem = arrayListOf<RssItem>()
var rssSet = hashMapOf<String,RssDataItem>()
var rssUrls = arrayListOf<String>()
var feddsUrls = arrayListOf<String>()
var rssList = arrayListOf<RssDataItem>()
fun refreshSms() {
Executors.newSingleThreadScheduledExecutor().schedule({
@ -158,14 +168,15 @@ internal class LauncherHome : Fragment() {
batteryReceiver = BatteryReceiver(binding.batteryProgress)
mMissedCallsAdapter = MissedCallsAdapter(callList, requireContext())
mSmsLogsAdapter = SmsLogsAdapter(smsList, requireContext())
mRssAdapter = RssItemAdapter(rssList, requireContext())
binding.favAppsGroup.visibility = View.GONE
binding.mainList.layoutManager = LinearLayoutManagerWrapper(requireContext())
workmanager()?.getWorkInfosByTagLiveData(SMS_WORK_TAG)?.observeForever {
binding.recentSms.text = "최근 문자 [${smsList.size}]"
if (binding.recentSms.isChecked) chooseAdpater()
else if (missedCalls.size == 0) {
else if (binding.missedCalls.isChecked && missedCalls.size == 0) {
binding.recentSms.isChecked = true
chooseAdpater()
}
@ -175,7 +186,7 @@ internal class LauncherHome : Fragment() {
workmanager()?.getWorkInfosByTagLiveData(CALL_WORK_TAG)?.observeForever {
binding.missedCalls.text = "최근 통화 [${missedCalls.size}]"
if (missedCalls.size > 0) binding.missedCalls.isChecked = true
if (binding.recentSms.isChecked && missedCalls.size > 0) binding.missedCalls.isChecked = true
if (binding.missedCalls.isChecked) chooseAdpater()
it.clear()
}
@ -192,6 +203,7 @@ internal class LauncherHome : Fragment() {
lateinit var mMissedCallsAdapter : MissedCallsAdapter
lateinit var mSmsLogsAdapter : SmsLogsAdapter
lateinit var mRssAdapter : RssItemAdapter
@ -215,7 +227,8 @@ internal class LauncherHome : Fragment() {
false
}
}
binding.mainList.setHasFixedSize(true)
binding.mainList.setItemViewCacheSize(10)
binding.missedCalls.setOnClickListener {
binding.missedCalls.isChecked = true
chooseAdpater()
@ -225,9 +238,45 @@ internal class LauncherHome : Fragment() {
chooseAdpater()
}
binding.otherCheck.setOnClickListener {
if(rssSet.size < 10) {
rssSet.clear()
feddsUrls.clear()
feddsUrls.addAll(RssList.newsFeeds)
rssUrls.clear()
rssUrls.addAll(RssList.youtubeUrls)
doWebPare(RssList.youtubeUrls.get(0))
binding.otherCheck.isChecked = true
rssList.clear()
rssList.add(object : RssDataItem {
override fun title(): String {
return "waiting for data"
}
override fun thumbnailUrl(): String {
return ""
}
override fun originPage(): String {
return "waiting for data"
}
override fun description(): String {
return "waiting for data"
}
override fun pubDate(): Long {
return 0L
}
override fun category(): RssDataType {
return RssDataType.NO_DATA
}
})
doWebPare(RssList.youtubeUrls.removeFirst())
getFeeds(feddsUrls.removeFirst())
}
binding.mainList.adapter = mRssAdapter
}
binding.otherCheck.setOnLongClickListener {
@ -244,7 +293,41 @@ internal class LauncherHome : Fragment() {
// BLog.LOGE("intent >>> ${i.action}")
}
fun getFeeds(url : String) {
Executors.newSingleThreadScheduledExecutor().schedule( {
RssFeedsParser.getFeeds(url).apply {
}.filter { it.pubDate() >= (System.currentTimeMillis() - 1000L * 60L * 60L * 24 * 3)
}.forEach {
BLog.LOGE("getFeeds it >> ${Gson().toJson(it)}")
rssSet.put(it.originPage(), it)
}.apply {
if (feddsUrls.size > 0) {
getFeeds(feddsUrls.removeFirst())
}
if(rssUrls.size < 1 && feddsUrls.size < 1) {
rssList.clear()
rssSet.forEach { t, u ->
rssList.add(u)
}
rssList.sortByDescending { it.pubDate() }
Handler(Looper.getMainLooper()).post {
mRssAdapter.updateData(rssList)
}
}
}
},1,TimeUnit.SECONDS)
}
fun doWebPare(url : String) {
if (url.contains("youtube")) {
Executors.newSingleThreadScheduledExecutor().schedule( {
Jsoup.connect(url).get()?.apply {
ytChannel(this)
}
},1,TimeUnit.SECONDS)
return
}
BLog.LOGE("binding.otherCheck before ThreadRun")
binding.searcher01.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
@ -318,23 +401,29 @@ internal class LauncherHome : Fragment() {
if (missedCalls.size > 0 && isAdded && isResumed && isVisible) {
try {
callList.clear()
binding.mainList.adapter = mMissedCallsAdapter
if(binding.mainList.adapter is MissedCallsAdapter){
}else {
binding.mainList.adapter = mMissedCallsAdapter
}
missedCalls.forEach { t, u ->
callList.add(u)
}.apply {
callList.sortByDescending { it.date }
mMissedCallsAdapter.updateData(callList)
Handler(Looper.getMainLooper()).post {mMissedCallsAdapter.updateData(callList)}
}
} catch (e : Exception) {
}
}
} else if(binding.recentSms.isChecked){
if (smsList.size > 0 && isAdded && isResumed && isVisible) {
try {
binding.mainList.adapter = mSmsLogsAdapter
if(binding.mainList.adapter is SmsLogsAdapter){
}else {
binding.mainList.adapter = mSmsLogsAdapter
}
smsList.sortByDescending { it.rcvDate }
mSmsLogsAdapter.updateData(smsList)
Handler(Looper.getMainLooper()).post {mSmsLogsAdapter.updateData(smsList)}
} catch (e : Exception) {
}
@ -402,13 +491,13 @@ internal class LauncherHome : Fragment() {
gestureDistance: Double
): Boolean {
when(fingers) {
1 ->
2 ->
if (targetView?.equals(binding.batteryProgress) ?: false) {
expandNotificationPanel(requireContext())
} else {
expandNotificationPanel(requireContext())
}
2->{
4->{
var startIntene = Intent(Intent.ACTION_MAIN)
startIntene.setComponent(ComponentName("com.samsung.android.app.interpreter","com.samsung.android.app.interpreter.interpretation.view.InterpretationActivity"))
startActivity(startIntene)
@ -587,96 +676,8 @@ internal class LauncherHome : Fragment() {
jGuruTag(doc)
}
} else if (lastedFinishedPageUrl?.contains("youtube") == true) {
// BLog.LOGE("doc youtube >>>> ${result}")
val doc: Document = Jsoup.parse(result)
// BLog.LOGE("doc.html().contains(ytInitialData) ${doc.html().contains("ytInitialData", false)}\n${doc.html().toString().split("var ytInitialData")[1].split("</script>")[0]}")
doc.getElementsByTag("script").forEach {
if(it.html().contains("var ytInitialData", false)) {/**/
var ytInitialData = it.html().split("var ytInitialData = '")[1].split("'")[0].toString()
ytInitialData = ytInitialData.replace("\\x22", "\"")
.replace("\\x7b", "{")
.replace("\\x7d", "}")
.replace("\\x5b", "[")
.replace("\\x5d", "]")
// BLog.LOGE("doc youtube div >>>> ${StringEscapeUtils.escapeJson(ytInitialData)}")
// BLog.LOGE("doc youtube div >>>> ${StringEscapeUtils.escapeXml10(ytInitialData)}")
// BLog.LOGE("doc youtube div >>>> ${StringEscapeUtils.escapeJson(ytInitialData)}")
// BLog.LOGE("doc youtube div >>>> ${StringEscapeUtils.escapeHtml3(ytInitialData)}")
// BLog.LOGE("doc youtube div >>>> ${StringEscapeUtils.unescapeEcmaScript(ytInitialData)}")
var tempJSONObject : JSONObject? = null
JSONObject(StringEscapeUtils.unescapeEcmaScript(ytInitialData)).apply{
tempJSONObject = this
BLog.LOGE("tempJSONObject.toString() >>> ${tempJSONObject.toString()}")
val aRoot = Gson().fromJson(tempJSONObject.toString(), Root::class.java)
BLog.LOGE("aRoot >>> ${aRoot}")
BLog.LOGE("aRoot?.responseContext >>> ${aRoot.responseContext}")
BLog.LOGE("aRoot?.header ${aRoot?.header}")
BLog.LOGE("aRoot?.topbar ${aRoot?.topbar}")
BLog.LOGE("aRoot?.metadata ${aRoot?.metadata}")
BLog.LOGE("aRoot?.contents? >>> ${aRoot?.contents}")
BLog.LOGE("aRoot?.contents?.twoColumnBrowseResultsRenderer >>> ${aRoot?.contents?.twoColumnBrowseResultsRenderer}")
BLog.LOGE("aRoot?.contents?.twoColumnBrowseResultsRenderer.tabs >>> ${aRoot?.contents?.twoColumnBrowseResultsRenderer?.tabs}")
BLog.LOGE("aRoot?.microformat? >>> ${aRoot?.microformat}")
BLog.LOGE("aRoot?.onResponseReceivedActions? >>> ${aRoot?.onResponseReceivedActions}")
aRoot?.contents?.apply {
BLog.LOGE("aRoot?.contents? >>> ${this}")
BLog.LOGE("aRoot?.contents?.twoColumnBrowseResultsRenderer >>> ${twoColumnBrowseResultsRenderer}")
twoColumnBrowseResultsRenderer?.tabs?.forEach {
BLog.LOGE("it.sectionListRenderer?.contents? >>> ${it}")
BLog.LOGE("it.tabRenderer ${it.tabRenderer}")
BLog.LOGE("it.expandableTabRenderer ${it.expandableTabRenderer}")
}
}
}
// .keys().forEach {
// if (it.equals("contents")) {
// var child = tempJSONObject?.getJSONObject(it)
// if(child?.length() == 1 && child?.has("singleColumnBrowseResultsRenderer") == true) {
// BLog.LOGE("(singleColumnBrowseResultsRenderer >> ${child}")
// var singleColumnBrowseResultsRenderer = child?.getJSONObject("singleColumnBrowseResultsRenderer")
// if (singleColumnBrowseResultsRenderer?.has("tabs") == true) {
// var tabs : JSONArray? = singleColumnBrowseResultsRenderer?.getJSONArray("tabs")
// BLog.LOGE("(tabs >>>> ${tabs}")
// for ( i in 0..<(tabs?.length() ?: 0)) {
// var tabsChild = tabs?.getJSONObject(i)
// BLog.LOGE("tabsChild >> ${tabsChild}")
// if(tabsChild?.has("tabRenderer") == true) {
// var tabRenderer = tabsChild?.getJSONObject("tabRenderer")
// BLog.LOGE("tabRenderer >> ${tabRenderer}")
// tabRenderer?.keys()?.forEach {
// // BLog.LOGE("tabRenderer in key >> ${it}")
// if (tabRenderer?.get(it) is String) {
// BLog.LOGE("tabRenderer String in $it >> ${tabRenderer?.get(it)}")
// }
// else if (tabRenderer?.get(it) is JSONObject) {
// var obj = tabRenderer?.getJSONObject(it)
// BLog.LOGE("tabRenderer child JSONObject in $it >> ${obj}")
// var pkey = it
// obj?.keys()?.forEach {
// jsonObjLog(pkey,it,obj)
// }
// }
// else if (tabRenderer?.get(it) is JSONArray) {
// BLog.LOGE("tabRenderer JSONArray in $it >> ${tabRenderer?.get(it)}")
// } else {
// BLog.LOGE("tabRenderer else in $it >> ${tabRenderer?.get(it)}")
// }
// }
// }
// }
// }
// } else {
// child?.keys()?.forEach { childKey ->
// BLog.LOGE("ytInitialData >>> ${it} >>>> ${childKey}")
// }
// }
// }
// }
}
}
// ytChannel(doc.html())
ytChannel(doc)
}
BLog.LOGE("binding.otherCheck after ThreadRun")
}
@ -691,16 +692,22 @@ internal class LauncherHome : Fragment() {
var obj = jsonObject?.getJSONObject(key)
BLog.LOGE("jsonObjLog $pkey JSONObject in $key >> ${obj}")
obj?.keys()?.forEach {
jsonObjLog(key,it, obj)
// jsonObjLog(key,it, obj)
}
}
else if (jsonObject?.has(key) == true && jsonObject?.get(key) is JSONArray) {
BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${jsonObject?.getJSONArray(key)}")
var array = jsonObject?.getJSONArray(key)
for ( i in 0..<(array?.length() ?: 0)) {
var child = array?.getJSONObject(i)
child?.keys()?.forEach {
jsonObjLog(key,it,child)
if (array?.get(i) is String) {
} else if (array?.get(i) is JSONObject) {
var child = array?.getJSONObject(i)
child?.keys()?.forEach {
// jsonObjLog(key, it, child)
}
} else if (array?.get(i) is JSONArray) {
BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${array?.getJSONArray(i)}")
}
}
} else {
@ -708,43 +715,96 @@ internal class LauncherHome : Fragment() {
}
}
fun ytChannel(dom: String) {
fun ytChannel(doc: Document) {
BLog.LOGE("ytChannel >>>>> doc${doc.title()}")
try {
doc.getElementsByTag("script").forEach {
// BLog.LOGE("ytChannel >>>>> doc ${doc.title()} find script ${it}")
if(it.html().contains("var ytInitialData", false)) {/**/
BLog.LOGE("ytChannel >>>>> doc${doc.title()} find ytInitialData ${it} ")
var ytInitialData = it.html().split("var ytInitialData = ")[1].split("</script>")[0].toString()
// ytInitialData = ytInitialData.replace("\\x22", "\"")
// .replace("\\x7b", "{")
// .replace("\\x7d", "}")
// .replace("\\x5b", "[")
// .replace("\\x5d", "]")
BLog.LOGE("ytChannel >>>>> doc${doc.title()} find ytInitialData ${ytInitialData} ")
var tempJSONObject : JSONObject? = null
JSONObject(ytInitialData).apply{
tempJSONObject = this
val root = Gson().fromJson(tempJSONObject.toString(), Root::class.java)
BLog.LOGE("ytChannel >>>>> doc root ${Gson().toJson(root)} apply ")
(if (root?.contents?.singleColumnBrowseResultsRenderer?.tabs?.size ?: 0 > 0) {
val parserFactory = XmlPullParserFactory.newInstance()
val kXmlParser = parserFactory.newPullParser()
val stream: InputStream =
ByteArrayInputStream(dom.toByteArray(Charset.forName("utf-8")))
kXmlParser.setInput(stream, "utf-8")
var eventType = kXmlParser.eventType
var done = false
BLog.LOGE("ytChannel >>>>> doc singleColumnBrowseResultsRenderer apply ")
root?.contents?.singleColumnBrowseResultsRenderer?.tabs?.forEach {
it.tabRenderer?.content?.sectionListRenderer?.contents?.forEach {
BLog.LOGE("ytChannel >>>>> doc sectionListRenderer?.contents ${Gson().toJson(it)} apply ")
it.shelfRenderer?.content?.verticalListRenderer?.items?.forEach {
BLog.LOGE("ytChannel >>>>> doc verticalListRenderer?.items ${Gson().toJson(it)} apply ")
(it.compactVideoRenderer as? RssDataItem)?.let {
if(it.pubDate() >= (System.currentTimeMillis() - 1000L * 60L * 60L * 24 * 3)) {
rssSet.put(it.originPage(), it)
BLog.LOGE("ytChannel >>>>> doc RssDataItem ${Gson().toJson(it)} apply ")
}
}
}
}
}
} else {
BLog.LOGE("ytChannel >>>>> doc twoColumnBrowseResultsRenderer apply ")
root?.contents?.twoColumnBrowseResultsRenderer?.tabs?.forEach {
it.tabRenderer?.content?.sectionListRenderer?.contents?.forEach {
BLog.LOGE("ytChannel >>>>> doc sectionListRenderer?.contents ${Gson().toJson(it)} apply ")
it.itemSectionRenderer?.contents?.forEach {
BLog.LOGE("ytChannel >>>>> doc itemSectionRenderer?.items ${Gson().toJson(it)} apply ")
it.shelfRenderer?.content?.horizontalListRenderer?.items?.forEach {
(it.gridVideoRenderer as? RssDataItem)?.let {
if(it.pubDate() >= (System.currentTimeMillis() - 1000L * 60L * 60L * 24 * 3)) {
rssSet.put(it.originPage(), it)
BLog.LOGE("ytChannel >>>>> doc RssDataItem ${Gson().toJson(it)} apply ")
}
}
}
}
}
}
}).apply {
BLog.LOGE("ytChannel >>>>> doc${doc.title()} apply ")
// rssList.clear()
// rssSet.forEach { k,v ->
// rssList.add(element = v)
// }.apply {
// eventType이 End_Document가 아니고 done이 false일 경우 반복
while(eventType != XmlPullParser.END_DOCUMENT) {
// 이름을 저장하기 위한 변수
var name: String? = null
// eventType이 START_TAG인 경우
if(eventType == XmlPullParser.START_TAG) {
// getName() 메서드를 사용해 현재 요소(태그)의 이름을 반환
name = kXmlParser.name
BLog.LOGE("kXmlParser.name >> ${kXmlParser.name}")
BLog.LOGE("kXmlParser.name >> ${kXmlParser.text}")
BLog.LOGE("kXmlParser.name >> ${kXmlParser.attributeCount}")
// if(name == "city") {
// // getAttributeValue를 사용해 첫 번째 매개변수 및
// // 두 번째 매개변수로 식별된 특성 값 획득
// cityView.text = kXmlParser.getAttributeValue(null, "name")
// } else if(name == "temperature") {
// temperatureView.text = kXmlParser.getAttributeValue(null, "value")
// }
if(rssUrls.size > 0) {
try {
val dddd = rssUrls.removeFirst()
doWebPare(dddd)
} catch (e : Exception) { }
}
rssList.clear()
rssSet.forEach { k,v ->
rssList.add(element = v)
}
if(rssUrls.size < 1 && feddsUrls.size < 1) {
rssList.sortByDescending { it.pubDate() }
Handler(Looper.getMainLooper()).post {
mRssAdapter.updateData(rssList)
}
}
}
}
}
// 다음 이벤트로 넘기기
eventType = kXmlParser.next()
}
} catch(e: Exception) {
}
}
/* gestures on root view */
@SuppressLint("ClickableViewAccessibility")
private fun rootViewGestures() {

View File

@ -0,0 +1,15 @@
package rasel.lunar.launcher.todos
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.LinearLayoutManager
class LinearLayoutManagerWrapper : LinearLayoutManager {
constructor(context: Context) : super(context)
constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
override fun supportsPredictiveItemAnimations(): Boolean {
return false
}
}

View File

@ -0,0 +1,17 @@
package rasel.lunar.launcher.todos
enum class RssDataType {
NO_DATA,
YOUTUBE,
NewsFeed
}
interface RssDataItem {
fun title() : String
fun thumbnailUrl() : String
fun originPage() : String
fun description() : String
fun pubDate() : Long
fun category() : RssDataType
}

View File

@ -0,0 +1,186 @@
package rasel.lunar.launcher.todos
import android.util.Xml
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
import java.io.InputStream
import java.net.URL
import java.text.SimpleDateFormat
import java.util.Locale
object RssFeedsParser {
var parseDateFormat: SimpleDateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH)
fun getFeeds(url : String) : List<RssDataItem> {
var returnList = mutableListOf<RssDataItem>()
try {
returnList.addAll(parse(getInputStream(url)!!))
} catch (e : Exception) {
e.printStackTrace()
}
return returnList
}
private fun getInputStream(link: String?): InputStream? {
return try {
val url = URL(link)
url.openConnection().getInputStream()
} catch (ioException: IOException) {
ioException.printStackTrace()
null
}
}
@Throws(XmlPullParserException::class, IOException::class)
private fun parse(inputStream: InputStream): List<RssDataItem> {
return inputStream.use { stream ->
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput(stream, null)
parser.nextTag()
readFeed(parser)
}
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readFeed(parser: XmlPullParser): List<RssFeed> {
parser.require(XmlPullParser.START_TAG, null, "rss")
var title: String? = null
var link: String? = null
var date = 0L
var desc : String? = null
var source : String? = null
val items: MutableList<RssFeed> = ArrayList()
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType != XmlPullParser.START_TAG) {
continue
}
val name = parser.name
if (name == "title") {
title = readTitle(parser)
} else if (name == "link") {
link = readLink(parser)
} else if (name == "pubDate") {
try {
date = parseDateFormat.parse(readDate(parser))?.time ?: 0L
}catch (e : Exception) {
e.printStackTrace()
}
} else if (name == "description") {
desc = readDesc(parser)
} else if (name == "source") {
source = readThumbnail(parser)
}
if (title != null && link != null) {
val item = RssFeed(title, link)
item.pubDate = date
item.source = source
item.description = desc
items.add(item)
title = null
link = null
source = null
desc = null
date = 0
}
}
return items
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readLink(parser: XmlPullParser): String {
parser.require(XmlPullParser.START_TAG, null, "link")
val link = readText(parser)
parser.require(XmlPullParser.END_TAG, null, "link")
return link
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readTitle(parser: XmlPullParser): String {
parser.require(XmlPullParser.START_TAG, null, "title")
val title = readText(parser)
parser.require(XmlPullParser.END_TAG, null, "title")
return title
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readDate(parser: XmlPullParser): String {
parser.require(XmlPullParser.START_TAG, null, "pubDate")
val date = readText(parser)
parser.require(XmlPullParser.END_TAG, null, "pubDate")
return date
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readDesc(parser: XmlPullParser): String {
parser.require(XmlPullParser.START_TAG, null, "description")
val link = readText(parser)
parser.require(XmlPullParser.END_TAG, null, "description")
return link
}
@Throws(XmlPullParserException::class, IOException::class)
private fun readThumbnail(parser: XmlPullParser): String {
parser.require(XmlPullParser.START_TAG, null, "source")
val link = readText(parser)
parser.require(XmlPullParser.END_TAG, null, "source")
return link
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readText(parser: XmlPullParser): String {
var result = ""
if (parser.next() == XmlPullParser.TEXT) {
result = parser.text
parser.nextTag()
}
return result
}
}
class RssFeed : RssDataItem{
var title : String? = ""
var link : String? = ""
var guid : String? = ""
var description : String? = ""
var pubDate : Long = 0L
var source : String? = ""
constructor(title: String?,link: String?) {
this.link = link
this.title = title
}
override fun title(): String {
return title ?: ""
}
override fun thumbnailUrl(): String {
return source ?: ""
}
override fun originPage(): String {
return link ?: ""
}
override fun description(): String {
return description ?: ""
}
override fun pubDate(): Long {
return pubDate
}
override fun category(): RssDataType {
return RssDataType.NewsFeed
}
}

View File

@ -24,7 +24,9 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat.startActivity
import androidx.core.net.toUri
@ -39,14 +41,15 @@ import rasel.lunar.launcher.databinding.ListItemWithBinding
import rasel.lunar.launcher.home.RssItem
import rasel.lunar.launcher.home.RssTagItem
import rasel.lunar.launcher.utils.BLog
import rasel.lunar.launcher.utils.Hashids
import rasel.lunar.launcher.utils.RecentSmsLog
import java.text.SimpleDateFormat
import java.util.Date
internal class RssItemAdapter (
private val smsList: ArrayList<RssItem>,
private val context: Context) : RecyclerView.Adapter<RssItemAdapter.RssTag>() {
private val smsList: ArrayList<RssDataItem>,
private val context: Context) : RecyclerView.Adapter<RssTag>() {
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RssTag {
val binding = ListItemWithBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
@ -57,42 +60,48 @@ internal class RssItemAdapter (
return smsList.size
}
val dateFormat = SimpleDateFormat("hh:mm / yy - MM - dd")
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: RssTag, position: Int) {
val todo = smsList[position]
BLog.LOGE("rssitem >>>> ${Gson().toJson(todo)}")
holder.view.title.text = "${todo.title()}"
holder.view.desc.text = Html.fromHtml("${todo.description()}")
holder.view.date.text = "${dateFormat.format(Date(todo.pubDate()))}"
if(todo.thumbnailUrl()?.length ?: 0 > 6) {
Picasso.get().load(todo.thumbnailUrl().toUri()).into(holder.view.circlePreview)
holder.view.circlePreview.visibility = View.VISIBLE
} else {
holder.view.circlePreview.visibility = View.GONE
}
holder.view.itemText.text = "\u25CF ${Gson().toJson(todo)}"
Picasso.get().load(todo.image.toUri()).into(holder.view.circlePreview)
BLog.LOGE("holder.view.itemText.text >>> ${holder.view.itemText.text}")
holder.view.itemText.setOnLongClickListener {
holder.view.root.setOnLongClickListener {
val clipBoard =
lActivity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipBoard.setPrimaryClip(ClipData.newPlainText("", todo.image))
clipBoard.setPrimaryClip(ClipData.newPlainText("", todo.thumbnailUrl()))
true
}
}
inner class RssTag(var view: ListItemWithBinding) : RecyclerView.ViewHolder(view.root)
fun updateData(newList: List<RssItem>) {
val diffUtilResult = DiffUtil.calculateDiff(RssItemDiffUtil(smsList, newList))
diffUtilResult.dispatchUpdatesTo(this)
fun updateData(newList: List<RssDataItem>) {
DiffUtil.calculateDiff(RssItemDiffUtil(smsList, newList)).dispatchUpdatesTo(this)
}
}
class RssTag(var view: ListItemWithBinding) : RecyclerView.ViewHolder(view.root)
internal class RssItemDiffUtil(
private val oldList: List<RssItem>, private val newList: List<RssItem>
private val oldList: List<RssDataItem>, private val newList: List<RssDataItem>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].pageLink == newList[newItemPosition].pageLink
oldList[oldItemPosition].originPage() == newList[newItemPosition].originPage()
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].pageLink == newList[newItemPosition].pageLink
oldList[oldItemPosition].originPage() == newList[newItemPosition].originPage()
}

View File

@ -38,7 +38,7 @@ import java.util.Date
internal class SmsLogsAdapter(
private val smsList: ArrayList<RecentSmsLog>,
private val context: Context) : RecyclerView.Adapter<SmsLogsAdapter.SmsLogHolder>() {
private val context: Context) : RecyclerView.Adapter<SmsLogHolder>() {
private val currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer)
@ -93,7 +93,7 @@ internal class SmsLogsAdapter(
}
inner class SmsLogHolder(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root)
fun updateData(newList: List<RecentSmsLog>) {
val diffUtilResult = DiffUtil.calculateDiff(SmsDiffUtil(smsList, newList))
@ -140,7 +140,7 @@ internal class SmsLogsAdapter(
}
}
class SmsLogHolder(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root)
internal class SmsDiffUtil(
private val oldList: List<RecentSmsLog>, private val newList: List<RecentSmsLog>
) : DiffUtil.Callback() {

View File

@ -1,8 +1,10 @@
package rasel.lunar.launcher.todos
import org.json.JSONObject
import rasel.lunar.launcher.utils.BLog
import rasel.lunar.launcher.utils.beforeDay
import java.util.Date
class YoutubeData {
}
// import com.fasterxml.jackson.databind.ObjectMapper; // version 2.11.1
// import com.fasterxml.jackson.annotation.JsonProperty; // version 2.11.1
@ -24,6 +26,7 @@ class AccessibilityContext {
class AccessibilityData {
var label: String? = null
var accessibilityData: AccessibilityData? = null
var publishedTimeText : PublishedTimeText? = null
}
class Action {
@ -292,20 +295,22 @@ class ConnectionErrorMicrophoneLabel {
var runs: ArrayList<Run>? = null
}
class Content {
class Content : Content14() {
var sectionListRenderer: SectionListRenderer? = null
var horizontalListRenderer: HorizontalListRenderer? = null
var pageHeaderViewModel: PageHeaderViewModel? = null
var verticalListRenderer: HorizontalListRenderer? = null
var listViewModel: ListViewModel? = null
}
class Content14 {
open class Content14 {
var itemSectionRenderer: ItemSectionRenderer? = null
var continuationItemRenderer: ContinuationItemRenderer? = null
var channelFeaturedContentRenderer: ChannelFeaturedContentRenderer? = null
var shelfRenderer: ShelfRenderer? = null
var reelShelfRenderer: ReelShelfRenderer? = null
var twoColumnBrowseResultsRenderer: TwoColumnBrowseResultsRenderer? = null
var singleColumnBrowseResultsRenderer : TwoColumnBrowseResultsRenderer? = null
}
class ContentMetadataViewModel {
@ -497,21 +502,21 @@ class GridChannelRenderer {
var trackingParams: String? = null
}
class GridVideoRenderer {
var videoId: String? = null
var thumbnail: Thumbnail? = null
var title: Title? = null
var publishedTimeText: PublishedTimeText? = null
var navigationEndpoint: NavigationEndpoint? = null
class GridVideoRenderer : VideoRenderer() {
// var videoId: String? = null
// var thumbnail: Thumbnail? = null
// var title: Title? = null
// var publishedTimeText: PublishedTimeText? = null
// var navigationEndpoint: NavigationEndpoint? = null
var badges: ArrayList<Badge>? = null
var ownerBadges: ArrayList<OwnerBadge>? = null
var trackingParams: String? = null
var menu: Menu? = null
var thumbnailOverlays: ArrayList<ThumbnailOverlay>? = null
var viewCountText: ViewCountText? = null
var shortViewCountText: ShortViewCountText? = null
// var ownerBadges: ArrayList<OwnerBadge>? = null
// var trackingParams: String? = null
// var menu: Menu? = null
// var thumbnailOverlays: ArrayList<ThumbnailOverlay>? = null
// var viewCountText: ViewCountText? = null
// var shortViewCountText: ShortViewCountText? = null
var richThumbnail: RichThumbnail? = null
var shortBylineText: ShortBylineText? = null
// var shortBylineText: ShortBylineText? = null
}
class Header {
@ -625,10 +630,11 @@ class Item {
var menuNavigationItemRenderer: MenuNavigationItemRenderer? = null
var reelItemRenderer: ReelItemRenderer? = null
var compactLinkRenderer: CompactLinkRenderer? = null
var compactVideoRenderer : VideoRenderer? = null
}
class ItemSectionRenderer {
var contents: ArrayList<Content>? = null
var contents: ArrayList<Content14>? = null
var trackingParams: String? = null
var sectionIdentifier: String? = null
var targetId: String? = null
@ -1042,6 +1048,7 @@ class Properties {
class PublishedTimeText {
var simpleText: String? = null
var runs : ArrayList<Run>? = null
}
class QoeLoggingContext {
@ -1110,15 +1117,15 @@ class RichThumbnail {
}
class Root {
var responseContext: ResponseContext? = null
// var responseContext: ResponseContext? = null
var contents: Content14? = null
var header: Header? = null
var metadata: Metadata? = null
var trackingParams: String? = null
var topbar: Topbar? = null
// var trackingParams: String? = null
// var topbar: Topbar? = null
var microformat: Microformat? = null
var onResponseReceivedActions: ArrayList<OnResponseReceivedAction>? = null
var frameworkUpdates: FrameworkUpdates? = null
// var onResponseReceivedActions: ArrayList<OnResponseReceivedAction>? = null
// var frameworkUpdates: FrameworkUpdates? = null
}
class Run {
@ -1368,7 +1375,7 @@ class Text {
}
class Thumbnail {
var thumbnails: ArrayList<Thumbnail>? = null
var thumbnails: ArrayList<Thumbnail11>? = null
var isOriginalAspectRatio: Boolean = false
}
@ -1556,7 +1563,7 @@ class VideoCountText {
var runs: ArrayList<Run>? = null
}
class VideoRenderer {
open class VideoRenderer : RssDataItem {
var videoId: String? = null
var thumbnail: Thumbnail? = null
var title: Title? = null
@ -1576,6 +1583,70 @@ class VideoRenderer {
var channelThumbnailSupportedRenderers: ChannelThumbnailSupportedRenderers? = null
var thumbnailOverlays: ArrayList<ThumbnailOverlay>? = null
var avatar: Avatar? = null
override fun title(): String {
return title?.runs?.first()?.text ?: title?.accessibility?.label ?: ""
}
override fun thumbnailUrl(): String {
return thumbnail?.thumbnails?.first()?.url ?: ""
}
override fun originPage(): String {
return if (navigationEndpoint?.watchEndpoint?.videoId?.length ?: 0 > 3) {
"https://www.youtube.com/watch?v=".plus(navigationEndpoint?.watchEndpoint?.videoId)
} else if (navigationEndpoint?.commandMetadata?.webCommandMetadata?.url?.length ?: 0 > 3 ) {
"https://www.youtube.com".plus(navigationEndpoint?.commandMetadata?.webCommandMetadata?.url)
} else {
"https://youtube.com"
}
}
override fun description(): String {
return ""
}
override fun pubDate(): Long {
var date = Date()
var dateTime = date.time
var before = 0
try {
var targetDate = publishedTimeText?.simpleText ?: publishedTimeText?.runs?.first()?.text ?: ""
if (targetDate?.length ?: 0 > 1) {
BLog.LOGE("targetDate >>>> ${targetDate}")
var dateDesc = targetDate
// dateDesc = dateDesc!!.replace("스트리밍 시간:","").trim()
dateDesc = dateDesc!!.split("")[0].trim()
val dayString = dateDesc.replace("[^0-9]".toRegex(), "")
before = dayString.toInt()
BLog.LOGE("targetDate >>>> ${before}")
if (dateDesc.contains("")) {
before = 365 * before
dateTime = beforeDay(date, before)
} else if (dateDesc.contains("")) {
before = 30 * before
dateTime = beforeDay(date, before)
} else if (dateDesc.contains("")) {
before = 7 * before
dateTime = beforeDay(date, before)
} else if (dateDesc.contains("")) {
dateTime = beforeDay(date, before)
} else if (dateDesc.contains("시간")) {
dateTime = dateTime.minus(before.times(1000L * 60L * 60L))
} else if (dateDesc.contains("")) {
dateTime = dateTime.minus(before.times(1000L * 60L))
}
}
}catch (e : Exception) {
}
return dateTime
}
override fun category(): RssDataType {
return RssDataType.YOUTUBE
}
}
class ViewCountText {
@ -1667,3 +1738,15 @@ class YtConfigData {
var rootVisualElementType: Int = 0
}
class YoutubeData {
// var responseContext: ResponseContext? = null
var contents: JSONObject? = null
var header: JSONObject? = null
var metadata: JSONObject? = null
// var trackingParams: String? = null
// var topbar: Topbar? = null
var microformat: JSONObject? = null
// var onResponseReceivedActions: ArrayList<OnResponseReceivedAction>? = null
// var frameworkUpdates: FrameworkUpdates? = null
}

View File

@ -465,3 +465,86 @@ fun getContactId(contentResolver: ContentResolver, phoneNumber: String?): String
return contactName
}
class NewsFeedsGetter : Worker {
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
}
@SuppressLint("RestrictedApi")
override fun doWork(): Result {
BLog.LOGE("phNumber == onStart")
var dateParam = beforeDay(Date(),3).toString()
val managedCursor = lActivity?.contentResolver?.query(
Telephony.Sms.CONTENT_URI, arrayOf(
Telephony.Sms.THREAD_ID,
Telephony.Sms.ADDRESS,
Telephony.Sms.TYPE,
Telephony.Sms.DATE,
Telephony.Sms.DATE_SENT,
Telephony.Sms.BODY,
Telephony.Sms.PERSON,
), Telephony.Sms.DATE + "> ${dateParam}", null, Telephony.Sms.DEFAULT_SORT_ORDER)
if (managedCursor != null && managedCursor.isClosed == false) {
try {
smsList.clear()
val tid = managedCursor.getColumnIndex(Telephony.Sms.THREAD_ID)
val address = managedCursor.getColumnIndex(Telephony.Sms.ADDRESS)
val type = managedCursor.getColumnIndex(Telephony.Sms.TYPE)
val date = managedCursor.getColumnIndex(Telephony.Sms.DATE)
val sendDate = managedCursor.getColumnIndex(Telephony.Sms.DATE_SENT)
val bodyIdx = managedCursor.getColumnIndex(Telephony.Sms.BODY)
val name = managedCursor.getColumnIndex(Telephony.Sms.PERSON)
while (managedCursor.moveToNext()) {
val id = managedCursor.getString(tid) // mobile number
val phNumber = managedCursor.getString(address) // mobile number
val callType = managedCursor.getString(type) // call type
val reciveDate = managedCursor.getString(date) // call date
val sendedDate = managedCursor.getString(sendDate) // call date
val smsBody = managedCursor.getString(bodyIdx).replace("\n"," ")
val callerName = managedCursor.getString(name)
var dir: String = ""
val dircode = callType.toInt()
when (dircode) {
Telephony.Sms.MESSAGE_TYPE_ALL -> {dir = "MESSAGE_TYPE_ALL"}
Telephony.Sms.MESSAGE_TYPE_INBOX -> {dir = "MESSAGE_TYPE_INBOX"}
Telephony.Sms.MESSAGE_TYPE_SENT -> {dir = "MESSAGE_TYPE_SENT"}
Telephony.Sms.MESSAGE_TYPE_DRAFT -> {dir = "MESSAGE_TYPE_DRAFT"}
Telephony.Sms.MESSAGE_TYPE_OUTBOX -> {dir = "MESSAGE_TYPE_OUTBOX"}
Telephony.Sms.MESSAGE_TYPE_FAILED -> {dir = "MESSAGE_TYPE_FAILED"}
Telephony.Sms.MESSAGE_TYPE_QUEUED -> {dir = "MESSAGE_TYPE_QUEUED"}
}
var log = RecentSmsLog(
phNumber,
dir,
reciveDate,
sendedDate,
smsBody,
callerName ?: ""
)
log.id = id
log.isMms = false
// BLog.LOGE("RecentSmsGetter resultData put ${phNumber +"_"+ reciveDate} >>> ${log.toJson()}")
log.sender = getContactName(applicationContext.contentResolver,phNumber) ?: ""
smsList.add(log)
}
} catch (e: Exception) {
} finally {
managedCursor.close()
}
}
if (lActivity?.contentResolver != null) {
TestQueryHelper(lActivity?.contentResolver!!).query()
}
return Result.success()
}
}

View File

@ -0,0 +1,310 @@
package rasel.lunar.launcher.utils
import java.lang.Long.toHexString
class Hashids(salt: String = defaultSalt, minHashLength: Int = defaultMinimalHashLength, alphabet: String = defaultAlphabet) {
companion object {
const val defaultSalt = ""
const val defaultMinimalHashLength = 0
const val defaultAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
const val defaultSeparators = "cfhistuCFHISTU"
const val minimalAlphabetLength = 16
const val separatorDiv = 3.5
const val guardDiv = 12
const val version = "1.1.0"
private const val emptyString = ""
private const val space = " "
private const val maxNumber = 9007199254740992
}
private val finalSalt = whatSalt(salt)
private val finalHashLength = whatHashLength(minHashLength)
private val alphabetSeparatorsAndGuards = calculateAlphabetAndSeparators(alphabet)
private val finalAlphabet = alphabetSeparatorsAndGuards.alphabet
private val finalSeparators = alphabetSeparatorsAndGuards.separators
private val finalGuards = alphabetSeparatorsAndGuards.guards
/**
* Encodes numbers to string
*
* @param numbers the numbers to encode
* @return The encoded string
*/
fun encode(vararg numbers: Long): String = when {
numbers.isEmpty() -> emptyString
numbers.any { it > maxNumber } -> throw IllegalArgumentException("Number can not be greater than ${maxNumber}L")
else -> {
val numbersHash = numbers.indices
.map { (numbers[it] % (it + 100)).toInt() }
.sum()
val initialCharacter = finalAlphabet.toCharArray()[numbersHash % finalAlphabet.length]
val (encodedString, encodingAlphabet) = initialEncode(numbers.asList(), finalSeparators.toCharArray(), initialCharacter.toString(), 0, finalAlphabet, initialCharacter.toString())
val tempReturnString = addGuardsIfNecessary(encodedString, numbersHash)
val halfLength = finalAlphabet.length / 2
ensureMinimalLength(halfLength, encodingAlphabet, tempReturnString)
}
}
/**
* Decodes string to numbers
*
* @param hash the encoded string
* @return Decoded numbers
*/
fun decode(hash: String): LongArray = when {
hash.isEmpty() -> longArrayOf()
else -> {
val guardsRegex = "[$finalGuards]".toRegex()
val hashWithSpacesInsteadOfGuards = hash.replace(guardsRegex, space)
val initialSplit = hashWithSpacesInsteadOfGuards.split(space)
val (lottery, hashBreakdown) = extractLotteryCharAndHashArray(initialSplit)
val returnValue = unhashSubHashes(hashBreakdown.iterator(), lottery, mutableListOf(), finalAlphabet)
when {
encode(*returnValue) != hash -> longArrayOf()
else -> returnValue
}
}
}
private fun guardIndex(numbersHash: Int, returnString: String, index: Int): Int = (numbersHash + returnString.toCharArray()[index].toInt()) % finalGuards.length
/**
* Encoded hex string to string
*
* @param hex the hex string to encode
* @return The encoded string
*/
fun encodeHex(hex: String): String = when {
!hex.matches("^[0-9a-fA-F]+$".toRegex()) -> emptyString
else -> {
val toEncode = "[\\w\\W]{1,12}".toRegex().findAll(hex)
.map { it.groupValues }
.flatten()
.map { it.toLong(16) }
.toList()
.toLongArray()
encode(*toEncode)
}
}
/**
* Decodes string to hex numbers string
*
* @param hash the encoded string
* @return decoded hex numbers string
*/
fun decodeHex(hash: String): String = decode(hash)
.map { toHexString(it).substring(1) }
.toString()
private fun whatSalt(aSalt: String) = when {
aSalt.isEmpty() -> defaultSalt
else -> aSalt
}
private fun whatHashLength(aLength: Int) = when {
aLength > 0 -> aLength
else -> defaultMinimalHashLength
}
private fun calculateAlphabetAndSeparators(userAlphabet: String): AlphabetAndSeparators {
val uniqueAlphabet = unique(userAlphabet)
when {
uniqueAlphabet.length < minimalAlphabetLength -> throw IllegalArgumentException("alphabet must contain at least $minimalAlphabetLength unique characters")
uniqueAlphabet.contains(space) -> throw IllegalArgumentException("alphabet cannot contains spaces")
else -> {
val legalSeparators = defaultSeparators.toSet().intersect(uniqueAlphabet.toSet())
val alphabetWithoutSeparators = uniqueAlphabet.toSet().minus(legalSeparators).joinToString(emptyString)
val shuffledSeparators = consistentShuffle(legalSeparators.joinToString(emptyString), finalSalt)
val (adjustedAlphabet, adjustedSeparators) = adjustAlphabetAndSeparators(alphabetWithoutSeparators, shuffledSeparators)
val guardCount = Math.ceil(adjustedAlphabet.length.toDouble() / guardDiv).toInt()
return if (adjustedAlphabet.length < 3) {
val guards = adjustedSeparators.substring(0, guardCount)
val seps = adjustedSeparators.substring(guardCount)
AlphabetAndSeparators(adjustedAlphabet, seps, guards)
} else {
val guards = adjustedAlphabet.substring(0, guardCount)
val alphabet = adjustedAlphabet.substring(guardCount)
AlphabetAndSeparators(alphabet, adjustedSeparators, guards)
}
}
}
}
private fun adjustAlphabetAndSeparators(alphabetWithoutSeparators: String, shuffledSeparators: String): AlphabetAndSeparators =
if (shuffledSeparators.isEmpty() ||
(alphabetWithoutSeparators.length / shuffledSeparators.length).toFloat() > separatorDiv) {
val sepsLength = calculateSeparatorsLength(alphabetWithoutSeparators)
if (sepsLength > shuffledSeparators.length) {
val difference = sepsLength - shuffledSeparators.length
val seps = shuffledSeparators + alphabetWithoutSeparators.substring(0, difference)
val alpha = alphabetWithoutSeparators.substring(difference)
AlphabetAndSeparators(consistentShuffle(alpha, finalSalt), seps)
} else {
val seps = shuffledSeparators.substring(0, sepsLength)
AlphabetAndSeparators(consistentShuffle(alphabetWithoutSeparators, finalSalt), seps)
}
} else {
AlphabetAndSeparators(consistentShuffle(alphabetWithoutSeparators, finalSalt), shuffledSeparators)
}
private fun calculateSeparatorsLength(alphabet: String): Int = when (val s = Math.ceil(alphabet.length / separatorDiv).toInt()) {
1 -> 2
else -> s
}
private fun unique(input: String) = input.toSet().joinToString(emptyString)
private fun addGuardsIfNecessary(encodedString: String, numbersHash: Int): String =
if (encodedString.length < finalHashLength) {
val guard0 = finalGuards.toCharArray()[guardIndex(numbersHash, encodedString, 0)]
val retString = guard0 + encodedString
if (retString.length < finalHashLength) {
val guard2 = finalGuards.toCharArray()[guardIndex(numbersHash, retString, 2)]
retString + guard2
} else {
retString
}
} else {
encodedString
}
private fun extractLotteryCharAndHashArray(initialSplit: List<String>): Pair<Char, List<String>> {
val separatorsRegex = "[$finalSeparators]".toRegex()
val i = when {
initialSplit.size == 2 || initialSplit.size == 3 -> 1
else -> 0
}
val ithElementOfSplit = initialSplit[i]
val lotteryChar = ithElementOfSplit.first()
val finalBreakdown = ithElementOfSplit
.substring(1)
.replace(separatorsRegex, space)
.split(space)
return Pair(lotteryChar, finalBreakdown)
}
private tailrec fun unhashSubHashes(hashes: Iterator<String>, lottery: Char, currentReturn: MutableList<Long>, alphabet: String): LongArray {
return when {
hashes.hasNext() -> {
val subHash = hashes.next()
val buffer = "$lottery$finalSalt$alphabet"
val newAlphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length))
currentReturn.add(unhash(subHash, newAlphabet))
unhashSubHashes(hashes, lottery, currentReturn, newAlphabet)
}
else -> currentReturn.toLongArray()
}
}
private fun hash(input: Long, alphabet: String): String =
doHash(input, alphabet.toCharArray(), HashData(emptyString, input)).hash
private tailrec fun doHash(number: Long, alphabet: CharArray, data: HashData): HashData = when {
data.current > 0 -> {
val newHashCharacter = alphabet[(data.current % alphabet.size.toLong()).toInt()]
val newCurrent = data.current / alphabet.size
doHash(number, alphabet, HashData("$newHashCharacter${data.hash}", newCurrent))
}
else -> data
}
private fun unhash(input: String, alphabet: String): Long =
doUnhash(input.toCharArray(), alphabet, alphabet.length.toDouble(), 0, 0)
private tailrec fun doUnhash(input: CharArray, alphabet: String, alphabetLengthDouble: Double, currentNumber: Long, currentIndex: Int): Long =
when {
currentIndex < input.size -> {
val position = alphabet.indexOf(input[currentIndex])
val newNumber = currentNumber + (position * alphabetLengthDouble.pow((input.size.toDouble() - currentIndex - 1))).toLong()
doUnhash(input, alphabet, alphabetLengthDouble, newNumber, currentIndex + 1)
}
else -> currentNumber
}
fun Double.pow(d : Double) : Double{
return Math.pow(this,d)
}
private fun consistentShuffle(alphabet: String, salt: String) = when {
salt.isEmpty() -> alphabet
else -> {
val initial = ShuffleData(alphabet.toList(), salt, 0, 0)
shuffle(initial, alphabet.length - 1, 1).alphabet.joinToString(emptyString)
}
}
private tailrec fun shuffle(data: ShuffleData, currentPosition: Int, limit: Int): ShuffleData = when {
currentPosition < limit -> data
else -> {
val currentAlphabet = data.alphabet.toCharArray()
val saltReminder = data.saltReminder % data.salt.length
val asciiValue = data.salt[saltReminder].toInt()
val cumulativeValue = data.cumulative + asciiValue
val positionToSwap = (asciiValue + saltReminder + cumulativeValue) % currentPosition
currentAlphabet[positionToSwap] = currentAlphabet[currentPosition].also {
currentAlphabet[currentPosition] = currentAlphabet[positionToSwap]
}
shuffle(ShuffleData(currentAlphabet.toList(), data.salt, cumulativeValue, saltReminder + 1), currentPosition - 1, limit)
}
}
private tailrec fun initialEncode(numbers: List<Long>,
separators: CharArray,
bufferSeed: String,
currentIndex: Int,
alphabet: String,
currentReturnString: String): Pair<String, String> = when {
currentIndex < numbers.size -> {
val currentNumber = numbers[currentIndex]
val buffer = bufferSeed + finalSalt + alphabet
val nextAlphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length))
val last = hash(currentNumber, nextAlphabet)
val newReturnString = if (currentIndex + 1 < numbers.size) {
val nextNumber = currentNumber % (last.toCharArray()[0].toInt() + currentIndex)
val sepsIndex = (nextNumber % separators.size).toInt()
currentReturnString + last + separators[sepsIndex]
} else {
currentReturnString + last
}
initialEncode(numbers, separators, bufferSeed, currentIndex + 1, nextAlphabet, newReturnString)
}
else -> Pair(currentReturnString, alphabet)
}
private tailrec fun ensureMinimalLength(halfLength: Int, alphabet: String, returnString: String): String = when {
returnString.length < finalHashLength -> {
val newAlphabet = consistentShuffle(alphabet, alphabet)
val tempReturnString = newAlphabet.substring(halfLength) + returnString + newAlphabet.substring(0, halfLength)
val excess = tempReturnString.length - finalHashLength
val newReturnString = if (excess > 0) {
val position = excess / 2
tempReturnString.substring(position, position + finalHashLength)
} else {
tempReturnString
}
ensureMinimalLength(halfLength, newAlphabet, newReturnString)
}
else -> returnString
}
}
private data class AlphabetAndSeparators(val alphabet: String, val separators: String, val guards: String = "")
private data class ShuffleData(val alphabet: List<Char>, val salt: String, val cumulative: Int, val saltReminder: Int)
private data class HashData(val hash: String, val current: Long)

View File

@ -5,6 +5,7 @@ import org.jsoup.nodes.Document
import rasel.lunar.launcher.LauncherActivity.Companion.TEST_PAG
import rasel.lunar.launcher.home.LauncherHome.Companion.listItem
import rasel.lunar.launcher.home.RssItem
import java.net.URLEncoder
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@ -17,6 +18,16 @@ object RssList {
"https://www.youtube.com/@gyeomsonisnothing",
"https://www.youtube.com/@ddeunddeun"
)
val newsFeeds = arrayListOf(
"https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko",
"https://news.google.com/rss/search?q=${URLEncoder.encode("IT")}=ko&gl=KR&ceid=KR%3Ako/",
"https://news.google.com/rss/search?q=${URLEncoder.encode("영화")}=ko&gl=KR&ceid=KR%3Ako/",
"https://news.google.com/rss/search?q=${URLEncoder.encode("음악")}=ko&gl=KR&ceid=KR%3Ako/",
"https://news.google.com/rss/search?q=${URLEncoder.encode("날씨")}=ko&gl=KR&ceid=KR%3Ako/",
"https://news.google.com/rss/search?q=${URLEncoder.encode("테크")}=ko&gl=KR&ceid=KR%3Ako/",
"https://news.google.com/rss/search?q=${URLEncoder.encode("최신")}=ko&gl=KR&ceid=KR%3Ako/",
"https://news.google.com/rss/search?q=${URLEncoder.encode("경제")}=ko&gl=KR&ceid=KR%3Ako/",
)
}
object DocParserManager {

View File

@ -134,7 +134,7 @@
android:overScrollMode="never"
android:padding="20dp"
android:scrollbars="none"
app:layoutManager="LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/summaryChoose"

View File

@ -4,25 +4,41 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!--rasel.lunar.launcher.view.CircleImageView-->
<ImageView
<!--rasel.lunar.launcher.view.CircleImageView-->
<rasel.lunar.launcher.view.CircleImageView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/circle_preview"
android:layout_width="40dp"
android:layout_height="40dp"/>
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:layout_width="90dp"
android:layout_height="90dp"/>
<com.google.android.material.textview.MaterialTextView
android:id="@+id/itemText"
<TextView
android:id="@+id/title"
android:layout_width="@dimen/zero"
android:layout_height="40dp"
android:padding="@dimen/twelve"
android:lines="3"
android:layout_height="30dp"
android:gravity="center_vertical|right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/circle_preview"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/desc"
android:layout_width="@dimen/zero"
android:layout_height="43dp"
android:gravity="center_vertical|right"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toTopOf="@id/date"
app:layout_constraintLeft_toRightOf="@id/circle_preview"
app:layout_constraintRight_toRightOf="parent"/>
<TextView
android:id="@+id/date"
android:layout_width="@dimen/zero"
android:layout_height="30dp"
android:gravity="center_vertical|right"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintLeft_toRightOf="@id/circle_preview"
app:layout_constraintRight_toRightOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>