...
This commit is contained in:
parent
55f2c3272c
commit
75df1bf324
@ -88,5 +88,5 @@ dependencies {
|
||||
implementation ("androidx.work:work-runtime:2.9.1")
|
||||
implementation ("com.google.code.gson:gson:2.11.0")
|
||||
implementation ("io.realm.kotlin:library-base:2.1.0")
|
||||
|
||||
implementation ("org.jsoup:jsoup:1.18.1")
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
@ -21,12 +22,19 @@
|
||||
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
|
||||
<!-- api 33+ -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_MMS" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_SMS"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@ -45,6 +53,7 @@
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:largeHeap="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:screenOrientation="nosensor"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
@ -122,5 +131,19 @@
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<receiver android:name=".home.EndCallReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PHONE_STATE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name=".home.SMSReceiver"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BROADCAST_SMS">
|
||||
<intent-filter>
|
||||
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||
<action android:name="android.provider.Telephony.MMS_RECEIVED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
@ -73,6 +73,9 @@ internal class LauncherActivity : AppCompatActivity() {
|
||||
lateinit var viewPager: ViewPager2
|
||||
|
||||
companion object {
|
||||
val TEST_PAG = "https://jav.guru/"
|
||||
|
||||
val TEST_PAG2 = "https://torrentsee246.com/topic/index?category1=129&category2=132"
|
||||
@JvmStatic var lActivity: LauncherActivity? = null
|
||||
@JvmStatic var appWidgetManager: AppWidgetManager? = null
|
||||
@JvmStatic var appWidgetHost: WidgetHost? = null
|
||||
@ -119,13 +122,27 @@ internal class LauncherActivity : AppCompatActivity() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
BLog.LOGE("onResume")
|
||||
// askPermissions()
|
||||
}
|
||||
|
||||
private fun welcomeDialog() {
|
||||
getSharedPreferences(PREFS_FIRST_LAUNCH, 0).let {
|
||||
if (it.getBoolean(KEY_FIRST_LAUNCH, true)) {
|
||||
it.edit().putBoolean(KEY_FIRST_LAUNCH, false).apply()
|
||||
|
||||
var needAsk = if (
|
||||
this.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.RECEIVE_MMS) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
true
|
||||
} else false
|
||||
|
||||
|
||||
getSharedPreferences(PREFS_FIRST_LAUNCH, 0).let {
|
||||
if (it.getBoolean(KEY_FIRST_LAUNCH, true) || needAsk) {
|
||||
it.edit().putBoolean(KEY_FIRST_LAUNCH, false).apply()
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.welcome)
|
||||
.setMessage(R.string.welcome_description)
|
||||
@ -140,8 +157,26 @@ internal class LauncherActivity : AppCompatActivity() {
|
||||
/* ask for the permissions */
|
||||
private fun askPermissions() {
|
||||
/* phone permission */
|
||||
if (this.checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED || this.checkSelfPermission(Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
|
||||
this.requestPermissions(arrayOf(Manifest.permission.CALL_PHONE,Manifest.permission.READ_SMS), 1)
|
||||
|
||||
if (
|
||||
this.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.RECEIVE_MMS) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.RECEIVE_SMS) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED ||
|
||||
this.checkSelfPermission(Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
this.requestPermissions(arrayOf(
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.READ_CALL_LOG,
|
||||
Manifest.permission.CALL_PHONE,
|
||||
Manifest.permission.READ_SMS,
|
||||
Manifest.permission.READ_PHONE_STATE,
|
||||
Manifest.permission.RECEIVE_MMS,
|
||||
Manifest.permission.RECEIVE_SMS,
|
||||
Manifest.permission.CALL_PHONE,
|
||||
Manifest.permission.READ_SMS), 1)
|
||||
}
|
||||
/* modify system settings */
|
||||
if (!Settings.System.canWrite(this)) {
|
||||
|
||||
@ -194,13 +194,11 @@ internal class ContactMenu : BottomSheetDialogFragment() {
|
||||
binding.phoneNumber.text = contactPhoneNumber
|
||||
}
|
||||
|
||||
/* detailed info dialog */
|
||||
@SuppressLint("SetTextI18n")
|
||||
|
||||
private fun detailedInfo() {
|
||||
var intent = Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(ContactsContract.Contacts.CONTENT_URI.toString() + "/" + packageName));
|
||||
startActivity(intent);
|
||||
|
||||
}
|
||||
|
||||
/* activity browser dialog */
|
||||
|
||||
42
app/src/main/kotlin/rasel/lunar/launcher/apps/SmmsMenu.kt
Normal file
42
app/src/main/kotlin/rasel/lunar/launcher/apps/SmmsMenu.kt
Normal file
@ -0,0 +1,42 @@
|
||||
package rasel.lunar.launcher.apps
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
|
||||
import rasel.lunar.launcher.databinding.ContactMenuBinding
|
||||
|
||||
class SmmsMenu: BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: ContactMenuBinding
|
||||
private lateinit var msgId: String
|
||||
private lateinit var packageManager: PackageManager
|
||||
private lateinit var appInfo: ApplicationInfo
|
||||
private lateinit var defAppName: String
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = ContactMenuBinding.inflate(inflater, container, false)
|
||||
|
||||
/* get package name from fragment's tag */
|
||||
msgId = tag.toString()
|
||||
val resolver = lActivity!!.contentResolver
|
||||
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -25,12 +25,21 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.http.SslError
|
||||
import android.os.Bundle
|
||||
import android.provider.AlarmClock
|
||||
import android.telephony.TelephonyManager
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat.RECEIVER_EXPORTED
|
||||
@ -41,6 +50,11 @@ import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.TEST_PAG
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
|
||||
import rasel.lunar.launcher.R
|
||||
import rasel.lunar.launcher.databinding.LauncherHomeBinding
|
||||
@ -55,18 +69,28 @@ import rasel.lunar.launcher.helpers.UniUtils.Companion.biometricPromptInfo
|
||||
import rasel.lunar.launcher.helpers.UniUtils.Companion.canAuthenticate
|
||||
import rasel.lunar.launcher.helpers.UniUtils.Companion.expandNotificationPanel
|
||||
import rasel.lunar.launcher.helpers.UniUtils.Companion.lockMethod
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.lastedFinishedPageUrl
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.listItem
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.listTags
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.refreshCalls
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.refreshSms
|
||||
import rasel.lunar.launcher.home.weather.WeatherExecutor
|
||||
import rasel.lunar.launcher.qaccess.QuickAccess
|
||||
import rasel.lunar.launcher.settings.SettingsActivity
|
||||
import rasel.lunar.launcher.todos.MissedCallsAdapter
|
||||
import rasel.lunar.launcher.todos.RssItemAdapter
|
||||
import rasel.lunar.launcher.todos.RssTagAdapter
|
||||
import rasel.lunar.launcher.todos.SmsLogsAdapter
|
||||
import rasel.lunar.launcher.utils.BLog
|
||||
import rasel.lunar.launcher.utils.MissedCallGetter
|
||||
import rasel.lunar.launcher.utils.RecentSmsGetter
|
||||
import rasel.lunar.launcher.utils.RecentSmsLog
|
||||
import rasel.lunar.launcher.utils.SimpleFingerGestures
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
@ -78,12 +102,32 @@ internal class LauncherHome : Fragment() {
|
||||
private lateinit var batteryReceiver: BatteryReceiver
|
||||
private var shouldResume = true
|
||||
companion object {
|
||||
val SMS_WORK_TAG = "RecentSmsGetter"
|
||||
val CALL_WORK_TAG = "MissedCallGetter"
|
||||
var lastedFinishedPageUrl : String = ""
|
||||
private var mWorkManager: WorkManager? = null
|
||||
|
||||
var missedCalls = hashMapOf<String, MissedCall>()
|
||||
var callList = arrayListOf<MissedCall>()
|
||||
var smsList = arrayListOf<RecentSmsLog>()
|
||||
var listTags = arrayListOf<RssTagItem>()
|
||||
var listItem = arrayListOf<RssItem>()
|
||||
|
||||
fun refreshSms() {
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
mWorkManager?.cancelAllWorkByTag(SMS_WORK_TAG)
|
||||
mWorkManager?.enqueue(PeriodicWorkRequestBuilder<RecentSmsGetter>(30, TimeUnit.MINUTES).addTag(SMS_WORK_TAG).build())
|
||||
}, 2, TimeUnit.SECONDS)
|
||||
}
|
||||
fun refreshCalls() {
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
mWorkManager?.cancelAllWorkByTag(CALL_WORK_TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(CALL_WORK_TAG,ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,PeriodicWorkRequestBuilder<MissedCallGetter>(30, TimeUnit.MINUTES).addTag("MissedCallGetter").build())
|
||||
}, 2, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
||||
|
||||
private val nReceiver: NotificationReceiver? = null
|
||||
private var nReceiver: NotificationReceiver? = null
|
||||
|
||||
class NotificationReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
@ -94,8 +138,15 @@ internal class LauncherHome : Fragment() {
|
||||
// txtView.setText(temp)
|
||||
}
|
||||
}
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
fun workmanager() : WorkManager? {
|
||||
if (mWorkManager == null) {
|
||||
mWorkManager = WorkManager.getInstance(requireContext())
|
||||
}
|
||||
return mWorkManager
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
mWorkManager = WorkManager.getInstance(requireContext())
|
||||
binding = LauncherHomeBinding.inflate(inflater, container, false)
|
||||
fragManager = lActivity!!.supportFragmentManager
|
||||
settingsPrefs = requireContext().getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
@ -103,13 +154,10 @@ internal class LauncherHome : Fragment() {
|
||||
mMissedCallsAdapter = MissedCallsAdapter(callList, requireContext())
|
||||
mSmsLogsAdapter = SmsLogsAdapter(smsList, requireContext())
|
||||
binding.favAppsGroup.visibility = View.GONE
|
||||
mWorkManager = WorkManager.getInstance(requireContext())
|
||||
mWorkManager?.cancelAllWork()
|
||||
|
||||
|
||||
mWorkManager?.enqueue(PeriodicWorkRequestBuilder<RecentSmsGetter>(30, TimeUnit.SECONDS).addTag("RecentSmsGetter").build())
|
||||
mWorkManager?.enqueueUniquePeriodicWork("MissedCallGetter",ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,PeriodicWorkRequestBuilder<MissedCallGetter>(30, TimeUnit.SECONDS).addTag("MissedCallGetter").build())
|
||||
mWorkManager?.getWorkInfosByTagLiveData("RecentSmsGetter")?.observeForever {
|
||||
|
||||
workmanager()?.getWorkInfosByTagLiveData(SMS_WORK_TAG)?.observeForever {
|
||||
binding.recentSms.text = "최근 문자 [${smsList.size}]"
|
||||
if (binding.recentSms.isChecked) chooseAdpater()
|
||||
else if (missedCalls.size == 0) {
|
||||
@ -120,17 +168,20 @@ internal class LauncherHome : Fragment() {
|
||||
it.clear()
|
||||
}
|
||||
|
||||
mWorkManager?.getWorkInfosByTagLiveData("MissedCallGetter")?.observeForever {
|
||||
workmanager()?.getWorkInfosByTagLiveData(CALL_WORK_TAG)?.observeForever {
|
||||
binding.missedCalls.text = "최근 통화 [${missedCalls.size}]"
|
||||
if (missedCalls.size > 0) binding.missedCalls.isChecked = true
|
||||
if (binding.missedCalls.isChecked) chooseAdpater()
|
||||
it.clear()
|
||||
}
|
||||
|
||||
nReceiver = NotificationReceiver()
|
||||
val filter = IntentFilter()
|
||||
filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
registerReceiver(requireContext(),nReceiver, filter,RECEIVER_EXPORTED)
|
||||
|
||||
BLog.LOGE("onCreateView()")
|
||||
refreshSms()
|
||||
refreshCalls()
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@ -162,17 +213,74 @@ internal class LauncherHome : Fragment() {
|
||||
|
||||
binding.missedCalls.setOnClickListener {
|
||||
binding.missedCalls.isChecked = true
|
||||
BLog.LOGE("binding.missedCalls >> ${binding.missedCalls.isChecked}")
|
||||
chooseAdpater()
|
||||
}
|
||||
binding.recentSms.setOnClickListener {
|
||||
binding.recentSms.isChecked = true
|
||||
chooseAdpater()
|
||||
}
|
||||
binding.otherCheck.setOnClickListener {
|
||||
BLog.LOGE("binding.otherCheck before ThreadRun")
|
||||
|
||||
|
||||
binding.searcher01.webViewClient = object : WebViewClient() {
|
||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
||||
BLog.LOGE("binding.otherCheck searcher01 in onPageStarted ${url}")
|
||||
super.onPageStarted(view, url, favicon)
|
||||
}
|
||||
|
||||
override fun onReceivedSslError(
|
||||
view: WebView?,
|
||||
handler: SslErrorHandler?,
|
||||
error: SslError?
|
||||
) {
|
||||
handler?.proceed()
|
||||
}
|
||||
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
lastedFinishedPageUrl = url ?: ""
|
||||
BLog.LOGE("binding.otherCheck searcher01 in onPageFinished ${url}")
|
||||
view?.evaluateJavascript("function getAll() {\n" +
|
||||
" MyJavaScriptInterface.sendValueFromHtml(document.getElementsByTagName('html')[0].innerHTML)" +
|
||||
" };getAll()") { result ->
|
||||
(result as? String)?.let {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.searcher01.apply {
|
||||
this.addJavascriptInterface(MyJavaScriptInterface(this),"MyJavaScriptInterface")
|
||||
setBackgroundColor(Color.WHITE) // 백그라운드 색상 설정
|
||||
setLayerType(View.LAYER_TYPE_SOFTWARE, null) // 랜더링 이슈 해결
|
||||
try {
|
||||
settings.apply {
|
||||
javaScriptEnabled = true // 자바스크립트 사용 가능하도록 설정
|
||||
loadWithOverviewMode = true // 전체 웹페이지를 화면에 맞게 로드
|
||||
useWideViewPort = true // 화면에 맞게 페이지 확대/축소
|
||||
domStorageEnabled = true // DOM 저장소 사용 가능하도록 설정
|
||||
setSupportMultipleWindows(true)
|
||||
javaScriptCanOpenWindowsAutomatically = true // 팝업창 차단 해제
|
||||
cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK
|
||||
textZoom = 100 // system 에 의한 글꼴 변형 방지
|
||||
defaultTextEncodingName = "UTF-8" // 인코딩 설정
|
||||
allowContentAccess = true // 웹뷰를 통해 content url에 접근할지 여부
|
||||
layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING // 웹페이지의 레이아웃을 화면에 맞게 자동으로 조정
|
||||
}
|
||||
} catch (ignore: NoSuchMethodError) {}
|
||||
//TEST_PAG
|
||||
loadUrl(TEST_PAG) // 웹페이지 연결
|
||||
}
|
||||
}
|
||||
binding.summaryChoose.setOnCheckedChangeListener { group, checkedId ->
|
||||
chooseAdpater()
|
||||
}
|
||||
|
||||
// val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_SERVICE_EXAMPLE")
|
||||
// i.putExtra("command", "list")
|
||||
// lActivity?.sendBroadcast(i)
|
||||
// BLog.LOGE("intent >>> ${i.action}")
|
||||
}
|
||||
|
||||
|
||||
@ -181,12 +289,11 @@ internal class LauncherHome : Fragment() {
|
||||
if (missedCalls.size > 0 && isAdded && isResumed && isVisible) {
|
||||
try {
|
||||
callList.clear()
|
||||
BLog.LOGE("chooseAdpater callList >>> ${callList.size}")
|
||||
BLog.LOGE("chooseAdpater callList missedCalls >>> ${missedCalls.values.size}")
|
||||
binding.mainList.adapter = mMissedCallsAdapter
|
||||
missedCalls.forEach { t, u ->
|
||||
callList.add(u)
|
||||
}.apply {
|
||||
callList.sortByDescending { it.date }
|
||||
mMissedCallsAdapter.updateData(callList)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
@ -196,7 +303,6 @@ internal class LauncherHome : Fragment() {
|
||||
} else if(binding.recentSms.isChecked){
|
||||
if (smsList.size > 0 && isAdded && isResumed && isVisible) {
|
||||
try {
|
||||
BLog.LOGE("chooseAdpater smsList >>> ${smsList.size}")
|
||||
binding.mainList.adapter = mSmsLogsAdapter
|
||||
smsList.sortByDescending { it.rcvDate }
|
||||
mSmsLogsAdapter.updateData(smsList)
|
||||
@ -228,7 +334,7 @@ internal class LauncherHome : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private var mWorkManager: WorkManager? = null
|
||||
|
||||
|
||||
|
||||
|
||||
@ -376,6 +482,81 @@ internal class LauncherHome : Fragment() {
|
||||
|
||||
}
|
||||
|
||||
inner class MyJavaScriptInterface(val webView: WebView) {
|
||||
|
||||
@JavascriptInterface
|
||||
fun sendValueFromHtml(result: String) {
|
||||
var htmlString = result.replace("\\u003","<")
|
||||
val simpldateFormat = SimpleDateFormat("d MMM, yy",Locale.US)
|
||||
val doc: Document = Jsoup.parse(htmlString)
|
||||
// BLog.LOGE("binding.otherCheck in ThreadRun ${doc.body()}")
|
||||
if(lastedFinishedPageUrl?.contains("page") == true || lastedFinishedPageUrl?.equals(TEST_PAG) == true) {
|
||||
BLog.LOGE("do default parsing")
|
||||
BLog.LOGE("SimpleDateFormat D MM yy => ${SimpleDateFormat("d MMM yy",Locale.US).format(Date())}")
|
||||
val prevUrl = doc.getElementsByClass("prev").get(0).getElementsByAttribute("href").get(0).attr("href")
|
||||
BLog.LOGE("doc.getElementsByClass(prev).get(0).html() ${prevUrl}")
|
||||
doc.getElementsByClass("column").forEach {
|
||||
var title = it.getElementsByAttribute("title").get(0).text()
|
||||
var model = title.replace("[","").split("]")[0]
|
||||
var pageLink = it.getElementsByClass("imgg").get(0).getElementsByAttribute("href").get(0).attr("href")
|
||||
var imgg = it.getElementsByClass("imgg").get(0).getElementsByAttribute("src").get(0).attr("src")
|
||||
var tags = it.getElementsByClass("tags").get(0).text()
|
||||
var date = it.getElementsByClass("date").get(0).text()
|
||||
listItem.add(RssItem(model = model, title = title, pageLink = pageLink, image = imgg, tags = tags, date = simpldateFormat.parse(date).time))
|
||||
}.apply {
|
||||
BLog.LOGE("listItem.size >>> ${listItem.size}")
|
||||
if (prevUrl!=null && prevUrl.length > TEST_PAG.length && prevUrl.contains("/page/") && listItem.size < (24 * 5) ) {
|
||||
BLog.LOGE("listItem.size >>> ${listItem.size} do next ")
|
||||
webView.postDelayed({webView.loadUrl(prevUrl)}, 5000L)
|
||||
if(binding.mainList.adapter is RssTagAdapter) {
|
||||
binding.mainList?.post {
|
||||
(binding.mainList.adapter as RssItemAdapter).apply {
|
||||
updateData(listItem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.mainList?.post {
|
||||
binding.mainList.adapter = RssItemAdapter(listItem,requireContext()).apply {
|
||||
updateData(listItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listTags.sortByDescending { it.count }
|
||||
binding.mainList?.post {
|
||||
(binding.mainList.adapter as RssItemAdapter).apply {
|
||||
updateData(listItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(lastedFinishedPageUrl?.contains("/tags/") == true ){
|
||||
listTags.clear()
|
||||
doc.getElementsByTag("ul").forEach {
|
||||
it.children().forEach {
|
||||
if (it.tag().name.contains("li")) {
|
||||
listTags.add(
|
||||
RssTagItem(
|
||||
tagTitle = it.getElementsByTag("a").get(0).text(),
|
||||
link = it.getElementsByTag("a").get(0).attr("href")
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
listTags.sortByDescending { it.count }
|
||||
binding.mainList?.post {
|
||||
binding.mainList.adapter = RssTagAdapter(listTags, requireContext()).apply {
|
||||
updateData(listTags)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BLog.LOGE("binding.otherCheck after ThreadRun")
|
||||
}
|
||||
|
||||
}
|
||||
/* gestures on root view */
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun rootViewGestures() {
|
||||
@ -555,3 +736,59 @@ class MissedCall {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EndCallReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
BLog.LOGE("EndCallReceiver >>> ${intent}")
|
||||
val phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE) ?: return
|
||||
if (phoneState == TelephonyManager.EXTRA_STATE_IDLE) {
|
||||
refreshCalls()
|
||||
}
|
||||
}
|
||||
}
|
||||
class SMSReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
BLog.LOGE("SMSReceiver >>> ${intent}")
|
||||
refreshSms()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RssTagItem {
|
||||
var link : String = ""
|
||||
var tagTitle = ""
|
||||
var count = 0
|
||||
constructor(link: String, tagTitle: String) {
|
||||
this.link = link
|
||||
this.tagTitle = tagTitle
|
||||
if (tagTitle.contains("(") && tagTitle.contains(")")) {
|
||||
try {
|
||||
count = tagTitle.split("(")[1].split(")")[0].toInt()
|
||||
}catch (e : Exception) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
class RssItem {
|
||||
var model : String = ""
|
||||
var title : String = ""
|
||||
var pageLink : String = ""
|
||||
var image : String = ""
|
||||
var tags : String = ""
|
||||
var date : Long = 0L
|
||||
|
||||
constructor(
|
||||
model: String,
|
||||
title: String,
|
||||
pageLink: String,
|
||||
image: String,
|
||||
tags: String,
|
||||
date: Long
|
||||
) {
|
||||
this.model = model
|
||||
this.title = title
|
||||
this.pageLink = pageLink
|
||||
this.image = image
|
||||
this.tags = tags
|
||||
this.date = date
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,9 @@ package rasel.lunar.launcher.todos
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -35,6 +38,7 @@ import rasel.lunar.launcher.helpers.UniUtils.Companion.copyToClipboard
|
||||
import rasel.lunar.launcher.home.MissedCall
|
||||
import rasel.lunar.launcher.utils.BLog
|
||||
import rasel.lunar.launcher.utils.RecentSmsLog
|
||||
import rasel.lunar.launcher.utils.getContactId
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@ -58,7 +62,6 @@ internal class MissedCallsAdapter(
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: MissedCallsHolder, position: Int) {
|
||||
val todo = callList[position]
|
||||
BLog.LOGE("callList >>> ${callList[position]}")
|
||||
holder.view.itemText.text = "\u25CF ${if(todo.name.equals("unknown")) todo.number else { todo.name}} , ${todo.typeString} : ${todo.count} : ${todo.date}"
|
||||
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
@ -67,7 +70,17 @@ internal class MissedCallsAdapter(
|
||||
holder.view.itemText.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.itemText.setOnLongClickListener {
|
||||
copyToClipboard(context, todo.name)
|
||||
|
||||
// copyToClipboard(context, todo.name)
|
||||
var cId = getContactId(lActivity!!.contentResolver, todo.number)
|
||||
if (cId != null && cId.length > 0) {
|
||||
var intent = Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(ContactsContract.Contacts.CONTENT_URI.toString() + "/" + cId));
|
||||
lActivity?.startActivity(intent);
|
||||
} else {
|
||||
lActivity?.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:${todo.number}")))
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
134
app/src/main/kotlin/rasel/lunar/launcher/todos/RssItemAdapter.kt
Normal file
134
app/src/main/kotlin/rasel/lunar/launcher/todos/RssItemAdapter.kt
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Lunar Launcher
|
||||
* Copyright (C) 2022 Md Rasel Hossain
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package rasel.lunar.launcher.todos
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
|
||||
import rasel.lunar.launcher.R
|
||||
import rasel.lunar.launcher.databinding.ListItemBinding
|
||||
import rasel.lunar.launcher.home.RssItem
|
||||
import rasel.lunar.launcher.home.RssTagItem
|
||||
import rasel.lunar.launcher.utils.BLog
|
||||
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 currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer)
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RssTag {
|
||||
val binding = ListItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return RssTag(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return smsList.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RssTag, position: Int) {
|
||||
val todo = smsList[position]
|
||||
|
||||
holder.view.itemText.text = "\u25CF ${Gson().toJson(todo)}"
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
// holder.view.itemText.isSingleLine = false
|
||||
/* launch edit or update dialog on item click */
|
||||
// holder.view.itemText.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.itemText.setOnLongClickListener {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class RssTag(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
fun updateData(newList: List<RssItem>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(RssItemDiffUtil(smsList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
// smsList.clear()
|
||||
// smsList.addAll(newList)
|
||||
}
|
||||
|
||||
/* update dialog */
|
||||
private fun updateDialog(position: Int) {
|
||||
// val bottomSheetDialog = BottomSheetDialog(lActivity!!, R.style.BottomSheetDialog)
|
||||
// val dialogBinding = TodoDialogBinding.inflate(LayoutInflater.from(context))
|
||||
// bottomSheetDialog.setContentView(dialogBinding.root)
|
||||
// bottomSheetDialog.show()
|
||||
// bottomSheetDialog.dismissWithAnimation = true
|
||||
//
|
||||
// val databaseHandler = DatabaseHandler(context)
|
||||
// val todo = databaseHandler.todos[position]
|
||||
//
|
||||
// dialogBinding.apply {
|
||||
// deleteAllConfirmation.visibility = View.GONE
|
||||
// todoInput.setText(todo.name)
|
||||
// todoCancel.text = context.getString(R.string.delete)
|
||||
// todoCancel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
|
||||
// todoOk.text = context.getString(R.string.update)
|
||||
// }
|
||||
//
|
||||
// /* delete the item */
|
||||
// dialogBinding.todoCancel.setOnClickListener {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /* update the item */
|
||||
// dialogBinding.todoOk.setOnClickListener {
|
||||
// val updatedTodoString = Objects.requireNonNull(dialogBinding.todoInput.text).toString().trim { it <= ' ' }
|
||||
// if (updatedTodoString.isNotEmpty()) {
|
||||
// todo.name = updatedTodoString
|
||||
// databaseHandler.updateTodo(todo)
|
||||
// bottomSheetDialog.dismiss()
|
||||
// } else {
|
||||
// dialogBinding.todoInput.error = context.getString(R.string.empty_text_field)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class RssItemDiffUtil(
|
||||
private val oldList: List<RssItem>, private val newList: List<RssItem>
|
||||
) : 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
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].pageLink == newList[newItemPosition].pageLink
|
||||
}
|
||||
133
app/src/main/kotlin/rasel/lunar/launcher/todos/RssTagAdapter.kt
Normal file
133
app/src/main/kotlin/rasel/lunar/launcher/todos/RssTagAdapter.kt
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Lunar Launcher
|
||||
* Copyright (C) 2022 Md Rasel Hossain
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package rasel.lunar.launcher.todos
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.gson.Gson
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
|
||||
import rasel.lunar.launcher.R
|
||||
import rasel.lunar.launcher.databinding.ListItemBinding
|
||||
import rasel.lunar.launcher.home.RssTagItem
|
||||
import rasel.lunar.launcher.utils.BLog
|
||||
import rasel.lunar.launcher.utils.RecentSmsLog
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class RssTagAdapter(
|
||||
private val smsList: ArrayList<RssTagItem>,
|
||||
private val context: Context) : RecyclerView.Adapter<RssTagAdapter.RssTag>() {
|
||||
|
||||
private val currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer)
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RssTag {
|
||||
val binding = ListItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return RssTag(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return smsList.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RssTag, position: Int) {
|
||||
val todo = smsList[position]
|
||||
|
||||
holder.view.itemText.text = "\u25CF ${Gson().toJson(todo)}"
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
// holder.view.itemText.isSingleLine = false
|
||||
/* launch edit or update dialog on item click */
|
||||
// holder.view.itemText.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.itemText.setOnLongClickListener {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class RssTag(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
fun updateData(newList: List<RssTagItem>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(RssTagDiffUtil(smsList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
// smsList.clear()
|
||||
// smsList.addAll(newList)
|
||||
}
|
||||
|
||||
/* update dialog */
|
||||
private fun updateDialog(position: Int) {
|
||||
// val bottomSheetDialog = BottomSheetDialog(lActivity!!, R.style.BottomSheetDialog)
|
||||
// val dialogBinding = TodoDialogBinding.inflate(LayoutInflater.from(context))
|
||||
// bottomSheetDialog.setContentView(dialogBinding.root)
|
||||
// bottomSheetDialog.show()
|
||||
// bottomSheetDialog.dismissWithAnimation = true
|
||||
//
|
||||
// val databaseHandler = DatabaseHandler(context)
|
||||
// val todo = databaseHandler.todos[position]
|
||||
//
|
||||
// dialogBinding.apply {
|
||||
// deleteAllConfirmation.visibility = View.GONE
|
||||
// todoInput.setText(todo.name)
|
||||
// todoCancel.text = context.getString(R.string.delete)
|
||||
// todoCancel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
|
||||
// todoOk.text = context.getString(R.string.update)
|
||||
// }
|
||||
//
|
||||
// /* delete the item */
|
||||
// dialogBinding.todoCancel.setOnClickListener {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /* update the item */
|
||||
// dialogBinding.todoOk.setOnClickListener {
|
||||
// val updatedTodoString = Objects.requireNonNull(dialogBinding.todoInput.text).toString().trim { it <= ' ' }
|
||||
// if (updatedTodoString.isNotEmpty()) {
|
||||
// todo.name = updatedTodoString
|
||||
// databaseHandler.updateTodo(todo)
|
||||
// bottomSheetDialog.dismiss()
|
||||
// } else {
|
||||
// dialogBinding.todoInput.error = context.getString(R.string.empty_text_field)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class RssTagDiffUtil(
|
||||
private val oldList: List<RssTagItem>, private val newList: List<RssTagItem>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].link == newList[newItemPosition].link
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].link == newList[newItemPosition].link
|
||||
}
|
||||
@ -20,28 +20,20 @@ package rasel.lunar.launcher.todos
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
|
||||
import rasel.lunar.launcher.R
|
||||
import rasel.lunar.launcher.apps.AppsAdapter.Companion.appsSize
|
||||
import rasel.lunar.launcher.apps.AppsDiffUtil
|
||||
import rasel.lunar.launcher.apps.Packages
|
||||
import rasel.lunar.launcher.databinding.ListItemBinding
|
||||
import rasel.lunar.launcher.databinding.TodoDialogBinding
|
||||
import rasel.lunar.launcher.helpers.UniUtils.Companion.copyToClipboard
|
||||
import rasel.lunar.launcher.home.MissedCall
|
||||
import rasel.lunar.launcher.utils.BLog
|
||||
import rasel.lunar.launcher.utils.RecentSmsLog
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.List
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class SmsLogsAdapter(
|
||||
@ -56,24 +48,46 @@ internal class SmsLogsAdapter(
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
BLog.LOGE("SmsLogsAdapter smsList.size >>> ${smsList.size}")
|
||||
return smsList.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: SmsLogHolder, position: Int) {
|
||||
val todo = smsList[position]
|
||||
BLog.LOGE("SmsLogsAdapter smsList >>> ${smsList[position]}")
|
||||
|
||||
holder.view.itemText.text = "\u25CF ${todo.person} ${todo.addr} , ${todo.body} : ${SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Date(Math.max(todo.pstDate.toLong(),todo.rcvDate.toLong())))} : ${todo.type}"
|
||||
|
||||
if(todo.isMms) {
|
||||
var body = todo.mmsContents.get("text")?.joinToString("\n")?.replace("\n"," ")
|
||||
body = if (body?.length ?: 0 > 60) body?.substring(0,60).plus("...") else body
|
||||
holder.view.itemText.text = "\u25CF ${todo.person} ${todo.addr} , ${body} : ${
|
||||
SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(
|
||||
Date(
|
||||
Math.max(
|
||||
todo.pstDate.toLong(),
|
||||
todo.rcvDate.toLong()
|
||||
)
|
||||
)
|
||||
)
|
||||
} : ${todo.type}"
|
||||
} else {
|
||||
holder.view.itemText.text = "\u25CF ${todo.person} ${todo.addr} , ${todo.body} : ${
|
||||
SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(
|
||||
Date(
|
||||
Math.max(
|
||||
todo.pstDate.toLong(),
|
||||
todo.rcvDate.toLong()
|
||||
)
|
||||
)
|
||||
)
|
||||
} : ${todo.type}"
|
||||
}
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
// holder.view.itemText.isSingleLine = false
|
||||
/* launch edit or update dialog on item click */
|
||||
// holder.view.itemText.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.itemText.setOnLongClickListener {
|
||||
|
||||
var intent = Intent(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse("smsto:" + Uri.encode(todo.addr)));
|
||||
lActivity?.startActivity(intent);
|
||||
true
|
||||
}
|
||||
|
||||
@ -82,10 +96,8 @@ internal class SmsLogsAdapter(
|
||||
inner class SmsLogHolder(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
fun updateData(newList: List<RecentSmsLog>) {
|
||||
BLog.LOGE("SmsLogsAdapter smsList newList >> ${newList.size}")
|
||||
val diffUtilResult = DiffUtil.calculateDiff(SmsDiffUtil(smsList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
BLog.LOGE("SmsLogsAdapter smsList smsList >> ${smsList.size}")
|
||||
// smsList.clear()
|
||||
// smsList.addAll(newList)
|
||||
}
|
||||
|
||||
@ -6,21 +6,23 @@ import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.provider.CallLog
|
||||
import android.provider.ContactsContract.PhoneLookup
|
||||
import android.provider.Telephony
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import com.google.gson.Gson
|
||||
import rasel.lunar.launcher.LauncherActivity.Companion.lActivity
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.missedCalls
|
||||
import rasel.lunar.launcher.home.LauncherHome.Companion.smsList
|
||||
import rasel.lunar.launcher.home.MissedCall
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
class MissedCallGetter : Worker {
|
||||
|
||||
@ -102,7 +104,7 @@ class MissedCallGetter : Worker {
|
||||
SimpleDateFormat("yyy/MM/dd-HH:mm:ss").format(callDayTime)
|
||||
)
|
||||
}
|
||||
BLog.LOGE("missed put >>> ${missed.toJson()}")
|
||||
// BLog.LOGE("missed put >>> ${missed.toJson()}")
|
||||
missedCalls.put(phNumber, missed)
|
||||
}
|
||||
|
||||
@ -142,6 +144,7 @@ class RecentSmsGetter : Worker {
|
||||
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,
|
||||
@ -152,6 +155,7 @@ class RecentSmsGetter : Worker {
|
||||
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)
|
||||
@ -159,6 +163,7 @@ class RecentSmsGetter : Worker {
|
||||
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
|
||||
@ -185,7 +190,10 @@ class RecentSmsGetter : Worker {
|
||||
smsBody,
|
||||
callerName ?: ""
|
||||
)
|
||||
BLog.LOGE("RecentSmsGetter resultData put ${phNumber +"_"+ reciveDate} >>> ${log.toJson()}")
|
||||
log.id = id
|
||||
log.isMms = false
|
||||
// BLog.LOGE("RecentSmsGetter resultData put ${phNumber +"_"+ reciveDate} >>> ${log.toJson()}")
|
||||
log.sender = getContactName(applicationContext.contentResolver,phNumber) ?: ""
|
||||
smsList.add(log)
|
||||
|
||||
}
|
||||
@ -205,12 +213,15 @@ class RecentSmsGetter : Worker {
|
||||
|
||||
class RecentSmsLog {
|
||||
var id : String = ""
|
||||
var isMms : Boolean = false
|
||||
var addr : String = ""
|
||||
var type : String = ""
|
||||
var rcvDate : String = "0"
|
||||
var pstDate : String = "0"
|
||||
var body : String = ""
|
||||
var person : String = ""
|
||||
var mmsContents : HashMap<String?,ArrayList<String>> = hashMapOf()
|
||||
var sender : String = ""
|
||||
|
||||
constructor(
|
||||
addr: String,
|
||||
@ -226,13 +237,15 @@ class RecentSmsLog {
|
||||
this.pstDate = pstDate
|
||||
this.body = body
|
||||
this.person = person
|
||||
this.isMms = false
|
||||
}
|
||||
|
||||
constructor(id: String, sender: String, date: String, body: String) {
|
||||
constructor(id: String, sender: String, date: String, body: HashMap<String?,ArrayList<String>>) {
|
||||
this.id = id
|
||||
this.rcvDate = date
|
||||
this.body = body
|
||||
this.mmsContents = body
|
||||
this.addr = sender
|
||||
this.isMms = true
|
||||
}
|
||||
|
||||
fun toJson() : String {
|
||||
@ -259,14 +272,17 @@ class TestQueryHelper(
|
||||
val id = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Mms._ID))
|
||||
val date = cursor.getLong(cursor.getColumnIndexOrThrow(Telephony.Mms.DATE)) * 1000
|
||||
val sender : String = getSender(id) ?: throw NullPointerException("NotFound Sender For Mms")
|
||||
val body: String = getBody(id) ?: throw NullPointerException("NotFound Body by Mms")
|
||||
val body = getBody(id) ?: throw NullPointerException("NotFound Body by Mms")
|
||||
|
||||
return RecentSmsLog(
|
||||
id = id.toString(),
|
||||
date = date.toString(),
|
||||
body = body,
|
||||
sender = sender
|
||||
)
|
||||
).apply {
|
||||
isMms = true
|
||||
this.sender = getContactName(_contentResolver,addr) ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSender(id: Int): String? {
|
||||
@ -286,7 +302,7 @@ class TestQueryHelper(
|
||||
var sender = senderAddressCursor.getString(
|
||||
senderAddressCursor.getColumnIndexOrThrow(Telephony.Mms.Addr.ADDRESS)
|
||||
)
|
||||
BLog.LOGE("sender >> ${sender}")
|
||||
// BLog.LOGE("sender >> ${sender}")
|
||||
if (isSender) {
|
||||
return sender
|
||||
}
|
||||
@ -296,33 +312,103 @@ class TestQueryHelper(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getBody(id: Int): String? {
|
||||
private fun getMmsText(id: String): String {
|
||||
val partURI = Uri.parse("content://mms/part/$id")
|
||||
var `is`: InputStream? = null
|
||||
val sb = StringBuilder()
|
||||
try {
|
||||
`is` = _contentResolver.openInputStream(partURI)
|
||||
if (`is` != null) {
|
||||
val isr = InputStreamReader(`is`, "UTF-8")
|
||||
val reader = BufferedReader(isr)
|
||||
var temp = reader.readLine()
|
||||
while (temp != null) {
|
||||
sb.append(temp)
|
||||
temp = reader.readLine()
|
||||
BLog.LOGE("temp >>> ${temp}")
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
} finally {
|
||||
if (`is` != null) {
|
||||
try {
|
||||
`is`.close()
|
||||
} catch (e: IOException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
BLog.LOGE("sb >>> ${sb.toString()}")
|
||||
return sb.toString()
|
||||
}
|
||||
private fun getBody(id: Int): HashMap<String?,ArrayList<String>> {
|
||||
var returns = hashMapOf<String?,ArrayList<String>>()
|
||||
val text = arrayListOf<String>()
|
||||
val image = arrayListOf<String>()
|
||||
val audio = arrayListOf<String>()
|
||||
|
||||
val projection = arrayOf("*")
|
||||
val video = arrayListOf<String>()
|
||||
_contentResolver.query(
|
||||
Uri.parse("content://mms/part"),
|
||||
null,
|
||||
projection,
|
||||
"${Telephony.Mms.Part.MSG_ID} = ?",
|
||||
arrayOf(id.toString()),
|
||||
null
|
||||
)?.use { partsCursor ->
|
||||
if (partsCursor.moveToFirst()) {
|
||||
do {
|
||||
val partId: String = partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part._ID))
|
||||
val partContentType =
|
||||
partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_TYPE))
|
||||
if (partContentType == "text/plain") {
|
||||
var textBody =
|
||||
partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT))
|
||||
.replace("\n\n", "\n").replace("\n", " ")
|
||||
if (textBody.length > 60) {
|
||||
textBody = textBody.substring(0, Math.min(textBody.length, 60)).plus(" ... ")
|
||||
}
|
||||
BLog.LOGE("textBody >>> ${textBody}")
|
||||
// BLog.LOGE("partContentType >> ${partContentType}")
|
||||
|
||||
return textBody
|
||||
if(partContentType?.contains("text") == true) {
|
||||
// BLog.LOGE("partContentType text >> ${partContentType} :: ${partId}")
|
||||
val data =
|
||||
partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part._DATA))
|
||||
val textBody =
|
||||
partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT))
|
||||
if (data != null && data.length > 0) {
|
||||
text.add(getMmsText(partId))
|
||||
} else {
|
||||
text.add(textBody)
|
||||
}
|
||||
}
|
||||
else if(partContentType?.contains("image") == true) {
|
||||
// BLog.LOGE("partContentType image >> ${partContentType}:: ${partId}")
|
||||
image.add(partId)
|
||||
}
|
||||
else if(partContentType?.contains("audio") == true) {
|
||||
// BLog.LOGE("partContentType audio >> ${partContentType}:: ${partId}")
|
||||
audio.add(partId)
|
||||
}
|
||||
else if(partContentType?.contains("video") == true) {
|
||||
// BLog.LOGE("partContentType video >> ${partContentType}:: ${partId}")
|
||||
video.add(partId)
|
||||
}
|
||||
else {
|
||||
// BLog.LOGE("partContentType >> ${partContentType}:: ${partId}")
|
||||
}
|
||||
|
||||
// if (partContentType == "text/plain") {
|
||||
// var textBody =
|
||||
// partsCursor.getString(partsCursor.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT))
|
||||
//// .replace("\n\n", "\n").replace("\n", " ")
|
||||
//// if (textBody.length > 60) {
|
||||
//// textBody = textBody.substring(0, Math.min(textBody.length, 60)).plus(" ... ")
|
||||
//// }
|
||||
//// BLog.LOGE("textBody >>> ${textBody}")
|
||||
// return Pair(textBody , partContentType)
|
||||
// }
|
||||
} while (partsCursor.moveToNext())
|
||||
}
|
||||
}
|
||||
return null
|
||||
returns.put("text", text)
|
||||
returns.put("image", image)
|
||||
returns.put("audio", audio)
|
||||
returns.put("video", video)
|
||||
// BLog.LOGE("returns.get(text).join => ${text.size} :: ${text.joinToString("\n")}")
|
||||
return returns
|
||||
}
|
||||
|
||||
fun convertData(cursor: Cursor?) {
|
||||
@ -343,3 +429,39 @@ class TestQueryHelper(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getContactName(contentResolver: ContentResolver, phoneNumber: String?): String? {
|
||||
val cr = contentResolver
|
||||
val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber))
|
||||
val cursor =
|
||||
cr.query(uri, arrayOf(PhoneLookup.DISPLAY_NAME), null, null, null)
|
||||
?: return null
|
||||
var contactName: String? = null
|
||||
if (cursor.moveToFirst()) {
|
||||
contactName = cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME))
|
||||
}
|
||||
|
||||
if (cursor != null && !cursor.isClosed) {
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
return contactName
|
||||
}
|
||||
|
||||
fun getContactId(contentResolver: ContentResolver, phoneNumber: String?): String? {
|
||||
val cr = contentResolver
|
||||
val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber))
|
||||
val cursor =
|
||||
cr.query(uri, arrayOf(PhoneLookup._ID), null, null, null)
|
||||
?: return null
|
||||
var contactName: String? = null
|
||||
if (cursor.moveToFirst()) {
|
||||
contactName = cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup._ID))
|
||||
}
|
||||
|
||||
if (cursor != null && !cursor.isClosed) {
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
return contactName
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
|
||||
|
||||
class NLService : NotificationListenerService() {
|
||||
@ -17,7 +17,7 @@ class NLService : NotificationListenerService() {
|
||||
super.onCreate()
|
||||
nlservicereciver = NLServiceReceiver()
|
||||
val filter = IntentFilter()
|
||||
filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_SERVICE_EXAMPLE")
|
||||
// filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_SERVICE_EXAMPLE")
|
||||
registerReceiver(nlservicereciver, filter)
|
||||
}
|
||||
|
||||
@ -27,16 +27,16 @@ class NLService : NotificationListenerService() {
|
||||
}
|
||||
|
||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||
Log.i(TAG, "********** onNotificationPosted")
|
||||
Log.i(TAG, "ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName)
|
||||
BLog.LOGE( "********** onNotificationPosted")
|
||||
BLog.LOGE("ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName)
|
||||
val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
i.putExtra("notification_event", "onNotificationPosted :" + sbn.packageName + "\n")
|
||||
sendBroadcast(i)
|
||||
}
|
||||
|
||||
override fun onNotificationRemoved(sbn: StatusBarNotification) {
|
||||
Log.i(TAG, "********** onNOtificationRemoved")
|
||||
Log.i(TAG, "ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName)
|
||||
BLog.LOGE(TAG, "********** onNOtificationRemoved")
|
||||
BLog.LOGE( "ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName)
|
||||
val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
i.putExtra("notification_event", "onNotificationRemoved :" + sbn.packageName + "\n")
|
||||
|
||||
@ -45,22 +45,24 @@ class NLService : NotificationListenerService() {
|
||||
|
||||
internal inner class NLServiceReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
BLog.LOGE("intent >>> ${intent.action}")
|
||||
if (intent.getStringExtra("command") == "clearall") {
|
||||
this@NLService.cancelAllNotifications()
|
||||
} else if (intent.getStringExtra("command") == "list") {
|
||||
val i1 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
i1.putExtra("notification_event", "=====================")
|
||||
sendBroadcast(i1)
|
||||
// val i1 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i1.putExtra("notification_event", "=====================")
|
||||
// sendBroadcast(i1)
|
||||
var i = 1
|
||||
for (sbn in this@NLService.activeNotifications) {
|
||||
val i2 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
i2.putExtra("notification_event", i.toString() + " " + sbn.packageName + "\n")
|
||||
sendBroadcast(i2)
|
||||
i++
|
||||
BLog.LOGE("sbn >>> ${sbn.packageName} , ${Gson().toJson(sbn.notification.extras.keySet())}")
|
||||
// val i2 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i2.putExtra("notification_event", i.toString() + " " + sbn.packageName + "\n")
|
||||
// sendBroadcast(i2)
|
||||
// i++
|
||||
}
|
||||
val i3 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
i3.putExtra("notification_event", "===== Notification List ====")
|
||||
sendBroadcast(i3)
|
||||
// val i3 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i3.putExtra("notification_event", "===== Notification List ====")
|
||||
// sendBroadcast(i3)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,26 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<WebView
|
||||
android:layout_margin="30dp"
|
||||
android:id="@+id/searcher_01"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:alpha="0.01"
|
||||
android:layout_height="0dp"/>
|
||||
|
||||
|
||||
<View
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:background="#11000000"
|
||||
android:layout_height="0dp"/>
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/batteryProgress"
|
||||
@ -76,6 +96,7 @@
|
||||
android:layout_height="40dp"
|
||||
app:layout_constraintTop_toBottomOf="@+id/batteryProgress"
|
||||
>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/missedCalls"
|
||||
android:button="@null"
|
||||
@ -85,7 +106,9 @@
|
||||
android:layout_weight="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/otherCheck"
|
||||
android:gravity="center"
|
||||
android:button="@null"
|
||||
android:text="투두"
|
||||
@ -93,6 +116,7 @@
|
||||
android:layout_weight="1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/recentSms"
|
||||
android:gravity="center"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user