Compare commits
No commits in common. "90532cf501e5cf42e4d31a6e3ff128e839b323cf" and "e0c118a73ec50f7825472e86c7ba2e4706696883" have entirely different histories.
90532cf501
...
e0c118a73e
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,13 +1,3 @@
|
|||||||
**v123010041**
|
|
||||||
|
|
||||||
- Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
|
|
||||||
- fix: added POST_NOTIFICATIONS to fix notifications issues.
|
|
||||||
- fix: scrollable filter sheet.
|
|
||||||
- enhancement: Ellipsize chips text.
|
|
||||||
- Cleaning.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v122123641**
|
**v122123641**
|
||||||
|
|
||||||
- feat: Disable the failing source in the filter sheet.
|
- feat: Disable the failing source in the filter sheet.
|
||||||
|
@ -66,7 +66,7 @@ android {
|
|||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
compileSdk = 33
|
compileSdk = 33
|
||||||
buildToolsVersion = "33.0.0"
|
buildToolsVersion = "31.0.0"
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
@ -366,6 +366,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
|
|
||||||
fun fetchOnEmptyList() {
|
fun fetchOnEmptyList() {
|
||||||
binding.recyclerView.doOnNextLayout {
|
binding.recyclerView.doOnNextLayout {
|
||||||
|
// TODO: do if last element (or is empty ?)
|
||||||
getElementsAccordingToTab(true)
|
getElementsAccordingToTab(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,9 +539,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
R.id.refresh -> {
|
R.id.refresh -> {
|
||||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||||
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
||||||
|
// TODO: Use Dispatchers.IO
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val updatedRemote = repository.updateRemote()
|
val updatedRemote = repository.updateRemote()
|
||||||
if (updatedRemote) {
|
if (updatedRemote) {
|
||||||
|
// TODO: Send toast messages from the repository
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
R.string.refresh_success_response, Toast.LENGTH_LONG
|
R.string.refresh_success_response, Toast.LENGTH_LONG
|
||||||
|
@ -27,6 +27,7 @@ import org.acra.ACRA
|
|||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
|
||||||
class LoginActivity : AppCompatActivity(), DIAware {
|
class LoginActivity : AppCompatActivity(), DIAware {
|
||||||
@ -156,9 +157,44 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
val login = binding.loginView.text.toString().trim()
|
val login = binding.loginView.text.toString().trim()
|
||||||
val password = binding.passwordView.text.toString().trim()
|
val password = binding.passwordView.text.toString().trim()
|
||||||
|
|
||||||
failInvalidUrl(url)
|
var cancel = false
|
||||||
failLoginDetails(password, login)
|
var focusView: View? = null
|
||||||
|
|
||||||
|
if (url.isBaseUrlInvalid()) {
|
||||||
|
binding.urlView.error = getString(R.string.login_url_problem)
|
||||||
|
focusView = binding.urlView
|
||||||
|
cancel = true
|
||||||
|
inValidCount++
|
||||||
|
if (inValidCount == 3) {
|
||||||
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
|
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||||
|
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||||
|
alertDialog.setButton(
|
||||||
|
AlertDialog.BUTTON_NEUTRAL,
|
||||||
|
"OK"
|
||||||
|
) { dialog, _ -> dialog.dismiss() }
|
||||||
|
alertDialog.show()
|
||||||
|
inValidCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWithLogin) {
|
||||||
|
if (TextUtils.isEmpty(password)) {
|
||||||
|
binding.passwordView.error = getString(R.string.error_invalid_password)
|
||||||
|
focusView = binding.passwordView
|
||||||
|
cancel = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TextUtils.isEmpty(login)) {
|
||||||
|
binding.loginView.error = getString(R.string.error_field_required)
|
||||||
|
focusView = binding.loginView
|
||||||
|
cancel = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancel) {
|
||||||
|
focusView?.requestFocus()
|
||||||
|
} else {
|
||||||
showProgress(true)
|
showProgress(true)
|
||||||
|
|
||||||
repository.refreshLoginInformation(url, login, password)
|
repository.refreshLoginInformation(url, login, password)
|
||||||
@ -185,55 +221,6 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
showProgress(false)
|
showProgress(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun failLoginDetails(
|
|
||||||
password: String,
|
|
||||||
login: String
|
|
||||||
) {
|
|
||||||
var lastFocusedView: View? = null
|
|
||||||
var cancel = false
|
|
||||||
if (isWithLogin) {
|
|
||||||
if (TextUtils.isEmpty(password)) {
|
|
||||||
binding.passwordView.error = getString(R.string.error_invalid_password)
|
|
||||||
lastFocusedView = binding.passwordView
|
|
||||||
cancel = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(login)) {
|
|
||||||
binding.loginView.error = getString(R.string.error_field_required)
|
|
||||||
lastFocusedView = binding.loginView
|
|
||||||
cancel = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maybeCancelAndFocusView(cancel, lastFocusedView)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun failInvalidUrl(url: String) {
|
|
||||||
val focusView = binding.urlView
|
|
||||||
var cancel = false
|
|
||||||
if (url.isBaseUrlInvalid()) {
|
|
||||||
cancel = true
|
|
||||||
binding.urlView.error = getString(R.string.login_url_problem)
|
|
||||||
inValidCount++
|
|
||||||
if (inValidCount == 3) {
|
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
|
||||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
|
||||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
|
||||||
alertDialog.setButton(
|
|
||||||
AlertDialog.BUTTON_NEUTRAL,
|
|
||||||
"OK"
|
|
||||||
) { dialog, _ -> dialog.dismiss() }
|
|
||||||
alertDialog.show()
|
|
||||||
inValidCount = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
maybeCancelAndFocusView(cancel, focusView)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun maybeCancelAndFocusView(cancel: Boolean, focusView: View?) {
|
|
||||||
if (cancel) {
|
|
||||||
focusView?.requestFocus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showProgress(show: Boolean) {
|
private fun showProgress(show: Boolean) {
|
||||||
|
@ -37,14 +37,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
import(networkModule)
|
import(networkModule)
|
||||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||||
bind<Repository>() with singleton {
|
bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
|
||||||
Repository(
|
|
||||||
instance(),
|
|
||||||
instance(),
|
|
||||||
isConnectionAvailable,
|
|
||||||
instance()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||||
}
|
}
|
||||||
@ -66,12 +59,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
|
|
||||||
handleNotificationChannels()
|
handleNotificationChannels()
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(
|
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
|
||||||
AppLifeCycleObserver(
|
|
||||||
connectivityStatus,
|
|
||||||
repository
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
||||||
@ -100,36 +88,20 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
initAcra {
|
initAcra {
|
||||||
reportFormat = StringFormat.JSON
|
reportFormat = StringFormat.JSON
|
||||||
reportContent = listOf(
|
reportContent = listOf(
|
||||||
ReportField.REPORT_ID,
|
ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
|
||||||
ReportField.INSTALLATION_ID,
|
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
|
||||||
ReportField.APP_VERSION_CODE,
|
ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
|
||||||
ReportField.APP_VERSION_NAME,
|
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
|
||||||
ReportField.BUILD,
|
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
|
||||||
ReportField.ANDROID_VERSION,
|
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
|
||||||
ReportField.BRAND,
|
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
|
||||||
ReportField.PHONE_MODEL,
|
|
||||||
ReportField.AVAILABLE_MEM_SIZE,
|
|
||||||
ReportField.TOTAL_MEM_SIZE,
|
|
||||||
ReportField.STACK_TRACE,
|
|
||||||
ReportField.APPLICATION_LOG,
|
|
||||||
ReportField.LOGCAT,
|
|
||||||
ReportField.INITIAL_CONFIGURATION,
|
|
||||||
ReportField.CRASH_CONFIGURATION,
|
|
||||||
ReportField.IS_SILENT,
|
|
||||||
ReportField.USER_APP_START_DATE,
|
|
||||||
ReportField.USER_COMMENT,
|
|
||||||
ReportField.USER_CRASH_DATE,
|
|
||||||
ReportField.USER_EMAIL,
|
|
||||||
ReportField.CUSTOM_DATA
|
|
||||||
)
|
|
||||||
toast {
|
toast {
|
||||||
//required
|
//required
|
||||||
text = getString(R.string.crash_toast_text)
|
text = getString(R.string.crash_toast_text)
|
||||||
length = Toast.LENGTH_SHORT
|
length = Toast.LENGTH_SHORT
|
||||||
}
|
}
|
||||||
httpSender {
|
httpSender {
|
||||||
uri =
|
uri = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
|
||||||
"https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
|
|
||||||
basicAuthLogin = "LMTlLZuazADohTCm"
|
basicAuthLogin = "LMTlLZuazADohTCm"
|
||||||
basicAuthPassword = "he6ghHp83F0PYPfh"
|
basicAuthPassword = "he6ghHp83F0PYPfh"
|
||||||
httpMethod = HttpSender.Method.POST
|
httpMethod = HttpSender.Method.POST
|
||||||
@ -147,11 +119,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
|
|
||||||
val newItemsChannelname = getString(R.string.new_items_channel_sync)
|
val newItemsChannelname = getString(R.string.new_items_channel_sync)
|
||||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
val newItemsChannelmChannel = NotificationChannel(
|
val newItemsChannelmChannel = NotificationChannel(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
|
||||||
AppSettingsService.newItemsChannelId,
|
|
||||||
newItemsChannelname,
|
|
||||||
newItemsChannelimportance
|
|
||||||
)
|
|
||||||
|
|
||||||
notificationManager.createNotificationChannel(mChannel)
|
notificationManager.createNotificationChannel(mChannel)
|
||||||
notificationManager.createNotificationChannel(newItemsChannelmChannel)
|
notificationManager.createNotificationChannel(newItemsChannelmChannel)
|
||||||
@ -165,17 +133,13 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
if (e is NoClassDefFoundError && e.stackTrace.asList().any {
|
if (e is NoClassDefFoundError && e.stackTrace.asList().any {
|
||||||
it.toString().contains("android.view.ViewDebug")
|
it.toString().contains("android.view.ViewDebug")
|
||||||
}) {
|
}) {
|
||||||
// Nothing
|
|
||||||
} else {
|
} else {
|
||||||
oldHandler.uncaughtException(thread, e)
|
oldHandler.uncaughtException(thread, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLifeCycleObserver(
|
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
|
||||||
val connectivityStatus: ConnectivityStatus,
|
|
||||||
val repository: Repository
|
|
||||||
) : DefaultLifecycleObserver {
|
|
||||||
|
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
super.onResume(owner)
|
super.onResume(owner)
|
||||||
|
@ -184,11 +184,13 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
if (allItems[binding.pager.currentItem].starred) {
|
if (allItems[binding.pager.currentItem].starred) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.unstarr(allItems[binding.pager.currentItem])
|
repository.unstarr(allItems[binding.pager.currentItem])
|
||||||
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
afterUnsave()
|
afterUnsave()
|
||||||
} else {
|
} else {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.starr(allItems[binding.pager.currentItem])
|
repository.starr(allItems[binding.pager.currentItem])
|
||||||
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
afterSave()
|
afterSave()
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,13 @@ import org.kodein.di.instance
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params),
|
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
|
||||||
DIAware {
|
|
||||||
|
|
||||||
override val di by lazy { (applicationContext as MyApp).di }
|
override val di by lazy { (applicationContext as MyApp).di }
|
||||||
private val repository: Repository by instance()
|
private val repository : Repository by instance()
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
private val appSettingsService : AppSettingsService by instance()
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
|
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@ -63,12 +62,13 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleNewItemsNotification(
|
private fun handleNewItemsNotification(
|
||||||
newItems: List<SelfossModel.Item>?,
|
newItems: List<SelfossModel.Item>?,
|
||||||
notificationManager: NotificationManager
|
notificationManager: NotificationManager
|
||||||
) {
|
) {
|
||||||
|
// TODO: Check if this coroutine is actually required
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val apiItems = newItems.orEmpty()
|
val apiItems = newItems.orEmpty()
|
||||||
|
|
||||||
@ -84,14 +84,10 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
val pendingIntent: PendingIntent =
|
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
|
||||||
PendingIntent.getActivity(context, 0, intent, pflags)
|
|
||||||
|
|
||||||
val newItemsNotification =
|
val newItemsNotification =
|
||||||
NotificationCompat.Builder(
|
NotificationCompat.Builder(applicationContext, AppSettingsService.newItemsChannelId)
|
||||||
applicationContext,
|
|
||||||
AppSettingsService.newItemsChannelId
|
|
||||||
)
|
|
||||||
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||||
.setContentText(
|
.setContentText(
|
||||||
context.getString(
|
context.getString(
|
||||||
|
@ -28,9 +28,7 @@ import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||||
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
@ -57,8 +55,6 @@ import java.util.*
|
|||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
|
|
||||||
private const val IMAGE_JPG = "image/jpg"
|
|
||||||
|
|
||||||
class ArticleFragment : Fragment(), DIAware {
|
class ArticleFragment : Fragment(), DIAware {
|
||||||
private var fontSize: Int = 16
|
private var fontSize: Int = 16
|
||||||
private lateinit var item: SelfossModel.Item
|
private lateinit var item: SelfossModel.Item
|
||||||
@ -67,12 +63,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
private lateinit var contentSource: String
|
private lateinit var contentSource: String
|
||||||
private lateinit var contentImage: String
|
private lateinit var contentImage: String
|
||||||
private lateinit var contentTitle: String
|
private lateinit var contentTitle: String
|
||||||
private lateinit var allImages: ArrayList<String>
|
private lateinit var allImages : ArrayList<String>
|
||||||
private lateinit var fab: FloatingActionButton
|
private lateinit var fab: FloatingActionButton
|
||||||
private lateinit var textAlignment: String
|
private lateinit var textAlignment: String
|
||||||
private lateinit var binding: FragmentArticleBinding
|
private lateinit var binding: FragmentArticleBinding
|
||||||
|
|
||||||
override val di: DI by closestDI()
|
override val di : DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
private val appSettingsService: AppSettingsService by instance()
|
||||||
|
|
||||||
@ -81,7 +77,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
private var font = ""
|
private var font = ""
|
||||||
private var staticBar = false
|
private var staticBar = false
|
||||||
|
|
||||||
private val mercuryApi: MercuryApi by instance()
|
private val mercuryApi : MercuryApi by instance()
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -119,81 +115,6 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
|
|
||||||
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
||||||
|
|
||||||
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
|
|
||||||
|
|
||||||
if (staticBar) {
|
|
||||||
fab.hide()
|
|
||||||
floatingToolbar.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.source.text = contentSource
|
|
||||||
if (typeface != null) {
|
|
||||||
binding.source.typeface = typeface
|
|
||||||
}
|
|
||||||
|
|
||||||
handleContent()
|
|
||||||
|
|
||||||
binding.nestedScrollView.setOnScrollChangeListener(
|
|
||||||
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
|
||||||
if (scrollY > oldScrollY) {
|
|
||||||
floatingToolbar.hide()
|
|
||||||
fab.hide()
|
|
||||||
} else {
|
|
||||||
if (staticBar) {
|
|
||||||
floatingToolbar.show()
|
|
||||||
} else {
|
|
||||||
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
} catch (e: InflateException) {
|
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
|
||||||
AlertDialog.Builder(requireContext())
|
|
||||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
|
||||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
|
||||||
.setPositiveButton(
|
|
||||||
android.R.string.ok
|
|
||||||
) { _, _ ->
|
|
||||||
appSettingsService.disableArticleViewer()
|
|
||||||
requireActivity().finish()
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleContent() {
|
|
||||||
if (contentText.isEmptyOrNullOrNullString()) {
|
|
||||||
if (repository.isNetworkAvailable()) {
|
|
||||||
getContentFromMercury()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binding.titleView.text = contentTitle
|
|
||||||
if (typeface != null) {
|
|
||||||
binding.titleView.typeface = typeface
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlToWebview()
|
|
||||||
|
|
||||||
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
|
||||||
binding.imageView.visibility = View.VISIBLE
|
|
||||||
Glide
|
|
||||||
.with(requireContext())
|
|
||||||
.asBitmap()
|
|
||||||
.load(contentImage)
|
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(binding.imageView)
|
|
||||||
} else {
|
|
||||||
binding.imageView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleFloatingToolbar(): FloatingToolbar {
|
|
||||||
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
||||||
floatingToolbar.attachFab(fab)
|
floatingToolbar.attachFab(fab)
|
||||||
|
|
||||||
@ -233,11 +154,73 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemLongClick(item: MenuItem?) {
|
override fun onItemLongClick(item: MenuItem?) {
|
||||||
// We do nothing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return floatingToolbar
|
|
||||||
|
if (staticBar) {
|
||||||
|
fab.hide()
|
||||||
|
floatingToolbar.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.source.text = contentSource
|
||||||
|
if (typeface != null) {
|
||||||
|
binding.source.typeface = typeface
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentText.isEmptyOrNullOrNullString()) {
|
||||||
|
getContentFromMercury()
|
||||||
|
} else {
|
||||||
|
binding.titleView.text = contentTitle
|
||||||
|
if (typeface != null) {
|
||||||
|
binding.titleView.typeface = typeface
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlToWebview()
|
||||||
|
|
||||||
|
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
Glide
|
||||||
|
.with(requireContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(contentImage)
|
||||||
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(binding.imageView)
|
||||||
|
} else {
|
||||||
|
binding.imageView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.nestedScrollView.setOnScrollChangeListener(
|
||||||
|
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
|
if (scrollY > oldScrollY) {
|
||||||
|
floatingToolbar.hide()
|
||||||
|
fab.hide()
|
||||||
|
} else {
|
||||||
|
if (staticBar) {
|
||||||
|
floatingToolbar.show()
|
||||||
|
} else {
|
||||||
|
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
} catch (e: InflateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
|
AlertDialog.Builder(requireContext())
|
||||||
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
|
.setPositiveButton(android.R.string.ok
|
||||||
|
) { _, _ ->
|
||||||
|
appSettingsService.disableArticleViewer()
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshAlignment() {
|
private fun refreshAlignment() {
|
||||||
@ -249,6 +232,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentFromMercury() {
|
private fun getContentFromMercury() {
|
||||||
|
if (repository.isNetworkAvailable()) {
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -256,34 +240,34 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
val response = mercuryApi.query(url)
|
val response = mercuryApi.query(url)
|
||||||
if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
|
if (response.success && response.data != null && !response.data?.content.isNullOrEmpty()) {
|
||||||
binding.titleView.text = response.data!!.title.orEmpty()
|
binding.titleView.text = response.data!!.title.orEmpty()
|
||||||
|
try {
|
||||||
if (typeface != null) {
|
if (typeface != null) {
|
||||||
binding.titleView.typeface = typeface
|
binding.titleView.typeface = typeface
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > typeface")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Note: Mercury may return relative urls... If it does the url val will not be changed.
|
||||||
URL(response.data!!.url)
|
URL(response.data!!.url)
|
||||||
url = response.data!!.url
|
url = response.data!!.url
|
||||||
|
} catch (e: MalformedURLException) {
|
||||||
|
// Mercury returned a relative url
|
||||||
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > malformedurlexception")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
contentText = response.data!!.content.orEmpty()
|
contentText = response.data!!.content.orEmpty()
|
||||||
htmlToWebview()
|
htmlToWebview()
|
||||||
|
|
||||||
handleLeadImage(response)
|
|
||||||
|
|
||||||
binding.nestedScrollView.scrollTo(0, 0)
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
openInBrowserAfterFailing()
|
|
||||||
}
|
|
||||||
} catch (e: SocketTimeoutException) {
|
|
||||||
openInBrowserAfterFailing()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > contenttext or html")
|
||||||
openInBrowserAfterFailing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) {
|
|
||||||
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
|
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
|
||||||
|
try {
|
||||||
binding.imageView.visibility = View.VISIBLE
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
try {
|
||||||
Glide
|
Glide
|
||||||
.with(requireContext())
|
.with(requireContext())
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
@ -292,66 +276,32 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
)
|
)
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
.into(binding.imageView)
|
.into(binding.imageView)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleImageLoading() {
|
|
||||||
binding.webcontent.webViewClient = object : WebViewClient() {
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
|
|
||||||
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
|
||||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("Deprecated in Java")
|
|
||||||
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
|
|
||||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
|
||||||
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US)
|
|
||||||
.contains(".jpeg")
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
val image =
|
binding.nestedScrollView.scrollTo(0, 0)
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
binding.progressBar.visibility = View.GONE
|
||||||
return WebResourceResponse(
|
} catch (e: Exception) {
|
||||||
IMAGE_JPG,
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview")
|
||||||
"UTF-8",
|
|
||||||
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
|
|
||||||
)
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
|
|
||||||
}
|
}
|
||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
} else {
|
||||||
try {
|
openInBrowserAfterFailing()
|
||||||
val image =
|
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
|
||||||
return WebResourceResponse(
|
|
||||||
IMAGE_JPG,
|
|
||||||
"UTF-8",
|
|
||||||
getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
|
|
||||||
)
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
|
|
||||||
}
|
}
|
||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
} catch (e: SocketTimeoutException) {
|
||||||
try {
|
openInBrowserAfterFailing()
|
||||||
val image =
|
} catch (e: Exception) {
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > whole thing")
|
||||||
return WebResourceResponse(
|
openInBrowserAfterFailing()
|
||||||
IMAGE_JPG,
|
|
||||||
"UTF-8",
|
|
||||||
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
|
|
||||||
)
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.shouldInterceptRequest(view, url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,16 +324,54 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
binding.webcontent.settings.loadWithOverviewMode = true
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
binding.webcontent.settings.javaScriptEnabled = false
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
|
|
||||||
handleImageLoading()
|
binding.webcontent.webViewClient = object : WebViewClient() {
|
||||||
|
@Deprecated("Deprecated in Java")
|
||||||
|
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
|
||||||
|
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||||
|
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
val gestureDetector =
|
@Deprecated("Deprecated in Java")
|
||||||
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
|
||||||
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
|
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
|
||||||
|
try {
|
||||||
|
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
||||||
|
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
|
||||||
|
} catch ( e : ExecutionException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (url.lowercase(Locale.US).contains(".png")) {
|
||||||
|
try {
|
||||||
|
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
||||||
|
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
|
||||||
|
} catch ( e : ExecutionException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||||
|
try {
|
||||||
|
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
||||||
|
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
|
||||||
|
} catch ( e : ExecutionException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.shouldInterceptRequest(view, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
return performClick()
|
return performClick()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
|
||||||
|
|
||||||
binding.webcontent.settings.layoutAlgorithm =
|
binding.webcontent.settings.layoutAlgorithm =
|
||||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
@ -405,12 +393,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
||||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
"""<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet">
|
||||||
fontName.replace(
|
|
||||||
" ",
|
|
||||||
"+"
|
|
||||||
)
|
|
||||||
}" rel="stylesheet">
|
|
||||||
|<style>
|
|<style>
|
||||||
| * {
|
| * {
|
||||||
| font-family: '$fontName';
|
| font-family: '$fontName';
|
||||||
@ -434,12 +417,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| max-width: 100%;
|
| max-width: 100%;
|
||||||
| }
|
| }
|
||||||
| a {
|
| a {
|
||||||
| color: ${
|
| color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and resources.getColor(R.color.colorAccent)
|
|
||||||
)
|
|
||||||
} !important;
|
|
||||||
| }
|
| }
|
||||||
| *:not(a) {
|
| *:not(a) {
|
||||||
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
||||||
@ -450,26 +428,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| word-break: break-word;
|
| word-break: break-word;
|
||||||
| overflow:hidden;
|
| overflow:hidden;
|
||||||
| line-height: 1.5em;
|
| line-height: 1.5em;
|
||||||
| background-color: ${
|
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data
|
|
||||||
)
|
|
||||||
};
|
|
||||||
| }
|
| }
|
||||||
| body, html {
|
| body, html {
|
||||||
| background-color: ${
|
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
|
||||||
String.format(
|
| border-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data
|
|
||||||
)
|
|
||||||
} !important;
|
|
||||||
| border-color: ${
|
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data
|
|
||||||
)
|
|
||||||
} !important;
|
|
||||||
| padding: 0 !important;
|
| padding: 0 !important;
|
||||||
| margin: 0 !important;
|
| margin: 0 !important;
|
||||||
| }
|
| }
|
||||||
@ -479,12 +442,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| pre, code {
|
| pre, code {
|
||||||
| white-space: pre-wrap;
|
| white-space: pre-wrap;
|
||||||
| width:100%;
|
| width:100%;
|
||||||
| background-color: ${
|
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data
|
|
||||||
)
|
|
||||||
};
|
|
||||||
| }
|
| }
|
||||||
| </style>
|
| </style>
|
||||||
| $fontLinkAndStyle
|
| $fontLinkAndStyle
|
||||||
@ -500,12 +458,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
|
|
||||||
fun scrollDown() {
|
fun scrollDown() {
|
||||||
val height = binding.nestedScrollView.measuredHeight
|
val height = binding.nestedScrollView.measuredHeight
|
||||||
binding.nestedScrollView.smoothScrollBy(0, height / 2)
|
binding.nestedScrollView.smoothScrollBy(0, height/2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollUp() {
|
fun scrollUp() {
|
||||||
val height = binding.nestedScrollView.measuredHeight
|
val height = binding.nestedScrollView.measuredHeight
|
||||||
binding.nestedScrollView.smoothScrollBy(0, -height / 2)
|
binding.nestedScrollView.smoothScrollBy(0, -height/2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openInBrowserAfterFailing() {
|
private fun openInBrowserAfterFailing() {
|
||||||
@ -529,10 +487,9 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
|
|
||||||
fun performClick(): Boolean {
|
fun performClick(): Boolean {
|
||||||
if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||||
) {
|
|
||||||
|
|
||||||
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
|
val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
|
||||||
|
|
||||||
val intent = Intent(activity, ImageActivity::class.java)
|
val intent = Intent(activity, ImageActivity::class.java)
|
||||||
intent.putExtra("allImages", allImages)
|
intent.putExtra("allImages", allImages)
|
||||||
|
@ -6,7 +6,6 @@ import android.graphics.drawable.Drawable
|
|||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.GONE
|
import android.view.View.GONE
|
||||||
@ -14,7 +13,6 @@ import android.view.View.VISIBLE
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
@ -37,7 +35,6 @@ import org.kodein.di.instance
|
|||||||
|
|
||||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||||
|
|
||||||
private lateinit var binding: FilterFragmentBinding
|
|
||||||
override val di: DI by closestDI()
|
override val di: DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
|
|
||||||
@ -48,8 +45,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
binding =
|
val binding =
|
||||||
FilterFragmentBinding.inflate(
|
bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate(
|
||||||
inflater,
|
inflater,
|
||||||
container,
|
container,
|
||||||
false
|
false
|
||||||
@ -57,36 +54,64 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
|
|
||||||
val context: Context? = context
|
val context: Context? = context
|
||||||
|
|
||||||
|
val tagGroup = binding.tagsGroup
|
||||||
|
val sourceGroup = binding.sourcesGroup
|
||||||
|
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
dismiss()
|
dismiss()
|
||||||
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
|
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
|
||||||
} else {
|
} else {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
handleTagChips(context)
|
val tags = repository.getTags()
|
||||||
handleSourceChips(context)
|
|
||||||
|
|
||||||
binding.progressBar2.visibility = GONE
|
tags.forEach { tag ->
|
||||||
binding.filterView.visibility = VISIBLE
|
val c = Chip(context)
|
||||||
|
c.text = tag.tag
|
||||||
|
|
||||||
|
try {
|
||||||
|
val gd = GradientDrawable()
|
||||||
|
val gdColor = try {
|
||||||
|
Color.parseColor(tag.color)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
|
||||||
|
resources.getColor(R.color.colorPrimary)
|
||||||
}
|
}
|
||||||
|
gd.setColor(gdColor)
|
||||||
|
gd.shape = GradientDrawable.RECTANGLE
|
||||||
|
gd.setSize(30, 30)
|
||||||
|
gd.cornerRadius = 30F
|
||||||
|
c.chipIcon = gd
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.floatingActionButton2.setOnClickListener {
|
c.setOnCloseIconClickListener {
|
||||||
(activity as HomeActivity).getElementsAccordingToTab()
|
(it as Chip).isCloseIconVisible = false
|
||||||
(activity as HomeActivity).fetchOnEmptyList()
|
selectedChip = null
|
||||||
dismiss()
|
repository.setTagFilter(null)
|
||||||
}
|
|
||||||
return binding.root
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSourceChips(
|
c.setOnClickListener {
|
||||||
context: Context
|
if (selectedChip != null) {
|
||||||
) {
|
selectedChip!!.isCloseIconVisible = false
|
||||||
val sourceGroup = binding.sourcesGroup
|
}
|
||||||
|
(it as Chip).isCloseIconVisible = true
|
||||||
|
selectedChip = it
|
||||||
|
repository.setTagFilter(tag)
|
||||||
|
|
||||||
|
repository.setSourceFilter(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repository.tagFilter.value?.equals(tag) == true) {
|
||||||
|
c.isCloseIconVisible = true
|
||||||
|
selectedChip = c
|
||||||
|
}
|
||||||
|
|
||||||
|
tagGroup.addView(c)
|
||||||
|
}
|
||||||
|
|
||||||
repository.getSources().forEach { source ->
|
repository.getSources().forEach { source ->
|
||||||
val c = Chip(context)
|
val c = Chip(context)
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
|
||||||
|
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
.load(source.getIcon(repository.baseUrl))
|
.load(source.getIcon(repository.baseUrl))
|
||||||
@ -149,61 +174,18 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
|
|
||||||
sourceGroup.addView(c)
|
sourceGroup.addView(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.progressBar2.visibility = GONE
|
||||||
|
binding.filterView.visibility = VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleTagChips(
|
binding.floatingActionButton2.setOnClickListener {
|
||||||
context: Context,
|
(activity as HomeActivity).getElementsAccordingToTab()
|
||||||
) {
|
(activity as HomeActivity).fetchOnEmptyList()
|
||||||
val tagGroup = binding.tagsGroup
|
dismiss()
|
||||||
|
|
||||||
val tags = repository.getTags()
|
|
||||||
|
|
||||||
tags.forEach { tag ->
|
|
||||||
val c = Chip(context)
|
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
|
||||||
c.text = tag.tag
|
|
||||||
|
|
||||||
try {
|
|
||||||
val gd = GradientDrawable()
|
|
||||||
val gdColor = try {
|
|
||||||
Color.parseColor(tag.color)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
|
|
||||||
resources.getColor(R.color.colorPrimary)
|
|
||||||
}
|
|
||||||
gd.setColor(gdColor)
|
|
||||||
gd.shape = GradientDrawable.RECTANGLE
|
|
||||||
gd.setSize(30, 30)
|
|
||||||
gd.cornerRadius = 30F
|
|
||||||
c.chipIcon = gd
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.setOnCloseIconClickListener {
|
|
||||||
(it as Chip).isCloseIconVisible = false
|
|
||||||
selectedChip = null
|
|
||||||
repository.setTagFilter(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.setOnClickListener {
|
|
||||||
if (selectedChip != null) {
|
|
||||||
selectedChip!!.isCloseIconVisible = false
|
|
||||||
}
|
|
||||||
(it as Chip).isCloseIconVisible = true
|
|
||||||
selectedChip = it
|
|
||||||
repository.setTagFilter(tag)
|
|
||||||
|
|
||||||
repository.setSourceFilter(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repository.tagFilter.value?.equals(tag) == true) {
|
|
||||||
c.isCloseIconVisible = true
|
|
||||||
selectedChip = c
|
|
||||||
}
|
|
||||||
|
|
||||||
tagGroup.addView(c)
|
|
||||||
}
|
}
|
||||||
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -8,6 +8,7 @@ import bou.amine.apps.readerforselfossv2.utils.getImages
|
|||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import org.acra.ktx.sendSilentlyWithAcra
|
||||||
|
|
||||||
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
||||||
val imageUrls = this.getImages()
|
val imageUrls = this.getImages()
|
||||||
|
@ -21,6 +21,7 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
|
import org.kodein.di.instance
|
||||||
|
|
||||||
private const val TITLE_TAG = "settingsActivityTitle"
|
private const val TITLE_TAG = "settingsActivityTitle"
|
||||||
|
|
||||||
@ -145,12 +146,8 @@ class SettingsActivity : AppCompatActivity(),
|
|||||||
fontSize?.setOnBindEditTextListener { editText ->
|
fontSize?.setOnBindEditTextListener { editText ->
|
||||||
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
editText.addTextChangedListener { object : TextWatcher {
|
editText.addTextChangedListener { object : TextWatcher {
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
// We do nothing
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
}
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
|
|
||||||
// We do nothing
|
|
||||||
}
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
override fun afterTextChanged(editable: Editable) {
|
||||||
try {
|
try {
|
||||||
editText.textSize = editable.toString().toInt().toFloat()
|
editText.textSize = editable.toString().toInt().toFloat()
|
||||||
|
@ -17,65 +17,13 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/filterView"
|
android:id="@+id/filterView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/filterTagsTitle"
|
|
||||||
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:text="@string/filter_item_tags"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/filterSourcesTitle"
|
|
||||||
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:text="@string/filter_item_sources"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
|
||||||
android:id="@+id/tagsGroup"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/filterTagsTitle"
|
|
||||||
app:singleSelection="true">
|
|
||||||
|
|
||||||
</com.google.android.material.chip.ChipGroup>
|
|
||||||
|
|
||||||
<com.google.android.material.chip.ChipGroup
|
|
||||||
android:id="@+id/sourcesGroup"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/filterSourcesTitle">
|
|
||||||
|
|
||||||
</com.google.android.material.chip.ChipGroup>
|
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/floatingActionButton2"
|
android:id="@+id/floatingActionButton2"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@ -90,7 +38,52 @@
|
|||||||
app:rippleColor="@color/colorAccentDark"
|
app:rippleColor="@color/colorAccentDark"
|
||||||
app:srcCompat="@drawable/ic_menu_search_white_24dp" />
|
app:srcCompat="@drawable/ic_menu_search_white_24dp" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
<TextView
|
||||||
</androidx.core.widget.NestedScrollView>
|
android:id="@+id/filterTagsTitle"
|
||||||
|
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="@string/filter_item_tags"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/tagsGroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/filterTagsTitle"
|
||||||
|
app:singleSelection="true">
|
||||||
|
|
||||||
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filterSourcesTitle"
|
||||||
|
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="@string/filter_item_sources"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/sourcesGroup"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/filterSourcesTitle">
|
||||||
|
|
||||||
|
</com.google.android.material.chip.ChipGroup>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -18,18 +18,6 @@ import org.junit.Assert.assertNotEquals
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
private const val BASE_URL = "https://test.com/selfoss/"
|
|
||||||
|
|
||||||
private const val SPOUT = "spouts\\rss\\fulltextrss"
|
|
||||||
|
|
||||||
private const val IMAGE_URL = "b3aa8a664d08eb15d6ff1db2fa83e0d9.png"
|
|
||||||
|
|
||||||
private const val IMAGE_URL_2 = "d8c92cdb1ef119ea85c4b9205c879ca7.png"
|
|
||||||
|
|
||||||
private const val FEED_URL = "https://test.com/feed"
|
|
||||||
|
|
||||||
private const val TAGS = "Test, New"
|
|
||||||
|
|
||||||
class RepositoryTest {
|
class RepositoryTest {
|
||||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||||
private val appSettingsService = mockk<AppSettingsService>()
|
private val appSettingsService = mockk<AppSettingsService>()
|
||||||
@ -57,7 +45,7 @@ class RepositoryTest {
|
|||||||
fun setup() {
|
fun setup() {
|
||||||
clearAllMocks()
|
clearAllMocks()
|
||||||
every { appSettingsService.getApiVersion() } returns 4
|
every { appSettingsService.getApiVersion() } returns 4
|
||||||
every { appSettingsService.getBaseUrl() } returns BASE_URL
|
every { appSettingsService.getBaseUrl() } returns "https://test.com/selfoss/"
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
|
|
||||||
@ -241,9 +229,9 @@ class RepositoryTest {
|
|||||||
1,
|
1,
|
||||||
"Test",
|
"Test",
|
||||||
listOf("tags"),
|
listOf("tags"),
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
"",
|
"",
|
||||||
IMAGE_URL,
|
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
))
|
))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@ -562,18 +550,18 @@ class RepositoryTest {
|
|||||||
1,
|
1,
|
||||||
"First source",
|
"First source",
|
||||||
listOf("Test", "second"),
|
listOf("Test", "second"),
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
"",
|
"",
|
||||||
IMAGE_URL_2,
|
"d8c92cdb1ef119ea85c4b9205c879ca7.png",
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
),
|
),
|
||||||
SelfossModel.Source(
|
SelfossModel.Source(
|
||||||
2,
|
2,
|
||||||
"Second source",
|
"Second source",
|
||||||
listOf("second"),
|
listOf("second"),
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
"",
|
"",
|
||||||
IMAGE_URL,
|
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -582,18 +570,18 @@ class RepositoryTest {
|
|||||||
"1",
|
"1",
|
||||||
"First DB source",
|
"First DB source",
|
||||||
"Test,second",
|
"Test,second",
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
"",
|
"",
|
||||||
IMAGE_URL_2,
|
"d8c92cdb1ef119ea85c4b9205c879ca7.png",
|
||||||
"url"
|
"url"
|
||||||
),
|
),
|
||||||
SOURCE(
|
SOURCE(
|
||||||
"2",
|
"2",
|
||||||
"Second source",
|
"Second source",
|
||||||
"second",
|
"second",
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
"",
|
"",
|
||||||
IMAGE_URL,
|
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
|
||||||
"url"
|
"url"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -732,9 +720,9 @@ class RepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.createSource(
|
response = repository.createSource(
|
||||||
"test",
|
"test",
|
||||||
FEED_URL,
|
"https://test.com/feed",
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
TAGS,
|
"Test, New",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -759,9 +747,9 @@ class RepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.createSource(
|
response = repository.createSource(
|
||||||
"test",
|
"test",
|
||||||
FEED_URL,
|
"https://test.com/feed",
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
TAGS
|
"Test, New"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,9 +774,9 @@ class RepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.createSource(
|
response = repository.createSource(
|
||||||
"test",
|
"test",
|
||||||
FEED_URL,
|
"https://test.com/feed",
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
TAGS
|
"Test, New"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -964,12 +952,12 @@ class RepositoryTest {
|
|||||||
coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
|
coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.refreshLoginInformation(BASE_URL, "login", "password")
|
repository.refreshLoginInformation("https://test.com/selfoss/", "login", "password")
|
||||||
|
|
||||||
coVerify(exactly = 1) { api.refreshLoginInformation() }
|
coVerify(exactly = 1) { api.refreshLoginInformation() }
|
||||||
coVerify(exactly = 1) {
|
coVerify(exactly = 1) {
|
||||||
appSettingsService.refreshLoginInformation(
|
appSettingsService.refreshLoginInformation(
|
||||||
BASE_URL,
|
"https://test.com/selfoss/",
|
||||||
"login",
|
"login",
|
||||||
"password"
|
"password"
|
||||||
)
|
)
|
||||||
@ -1043,9 +1031,9 @@ class RepositoryTest {
|
|||||||
1,
|
1,
|
||||||
"First source",
|
"First source",
|
||||||
listOf("Test", "second"),
|
listOf("Test", "second"),
|
||||||
SPOUT,
|
"spouts\\rss\\fulltextrss",
|
||||||
"",
|
"",
|
||||||
IMAGE_URL_2,
|
"d8c92cdb1ef119ea85c4b9205c879ca7.png",
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -7,8 +7,8 @@ buildscript {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
//trick: for the same plugin versions in all sub-modules
|
//trick: for the same plugin versions in all sub-modules
|
||||||
id("com.android.application").version("7.4.0").apply(false)
|
id("com.android.application").version("7.3.1").apply(false)
|
||||||
id("com.android.library").version("7.4.0").apply(false)
|
id("com.android.library").version("7.3.1").apply(false)
|
||||||
kotlin("android").version("1.7.20").apply(false)
|
kotlin("android").version("1.7.20").apply(false)
|
||||||
kotlin("multiplatform").version("1.7.20").apply(false)
|
kotlin("multiplatform").version("1.7.20").apply(false)
|
||||||
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
|
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
|
||||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Mon Jan 23 20:47:46 CET 2023
|
#Wed Feb 09 17:05:19 CET 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
@ -8,7 +8,7 @@ class MercuryModel {
|
|||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
val title: String?,
|
val title: String?,
|
||||||
val content: String?,
|
val content: String?,
|
||||||
val lead_image_url: String?, // NOSONAR
|
val lead_image_url: String?,
|
||||||
val url: String
|
val url: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,13 +81,19 @@ class SelfossModel {
|
|||||||
val tags: List<String>,
|
val tags: List<String>,
|
||||||
val author: String?
|
val author: String?
|
||||||
) {
|
) {
|
||||||
|
// TODO: maybe find a better way to handle these kind of urls
|
||||||
fun getLinkDecoded(): String {
|
fun getLinkDecoded(): String {
|
||||||
var stringUrl: String
|
var stringUrl: String
|
||||||
stringUrl = if (link.contains("//news.google.com/news/") && link.contains("&url=")) {
|
stringUrl =
|
||||||
|
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||||
|
if (link.contains("&url=")) {
|
||||||
link.substringAfter("&url=")
|
link.substringAfter("&url=")
|
||||||
} else {
|
} else {
|
||||||
this.link.replace("&", "&")
|
this.link.replace("&", "&")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.link.replace("&", "&")
|
||||||
|
}
|
||||||
|
|
||||||
// handle :443 => https
|
// handle :443 => https
|
||||||
if (stringUrl.contains(":443")) {
|
if (stringUrl.contains(":443")) {
|
||||||
|
@ -51,7 +51,9 @@ class Repository(
|
|||||||
private var _selectedSource: SelfossModel.Source? = null
|
private var _selectedSource: SelfossModel.Source? = null
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
|
// TODO: Use the updatedSince parameter
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||||
|
var fromDB = false
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
fetchedItems = api.getItems(
|
fetchedItems = api.getItems(
|
||||||
displayedItems.type,
|
displayedItems.type,
|
||||||
@ -61,7 +63,9 @@ class Repository(
|
|||||||
searchFilter,
|
searchFilter,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
} else {
|
||||||
|
if (appSettingsService.isItemCachingEnabled()) {
|
||||||
|
fromDB = true
|
||||||
var dbItems = getDBItems().filter {
|
var dbItems = getDBItems().filter {
|
||||||
displayedItems == ItemType.ALL ||
|
displayedItems == ItemType.ALL ||
|
||||||
(it.unread && displayedItems == ItemType.UNREAD) ||
|
(it.unread && displayedItems == ItemType.UNREAD) ||
|
||||||
@ -73,15 +77,17 @@ class Repository(
|
|||||||
if (sourceFilter.value != null) {
|
if (sourceFilter.value != null) {
|
||||||
dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
|
dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
|
||||||
}
|
}
|
||||||
val itemsList = ArrayList(dbItems.map { it.toView() })
|
|
||||||
itemsList.sortByDescending { DateUtils.parseDate(it.datetime) }
|
|
||||||
fetchedItems = StatusAndData.succes(
|
fetchedItems = StatusAndData.succes(
|
||||||
itemsList
|
dbItems.map { it.toView() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fetchedItems.success && fetchedItems.data != null) {
|
if (fetchedItems.success && fetchedItems.data != null) {
|
||||||
items = ArrayList(fetchedItems.data!!)
|
items = ArrayList(fetchedItems.data!!)
|
||||||
|
if (fromDB) {
|
||||||
|
items.sortByDescending { DateUtils.parseDate(it.datetime) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
@ -167,13 +173,14 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
|
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
|
||||||
return if (isNetworkAvailable()) {
|
return if (isNetworkAvailable()) {
|
||||||
val spouts = api.spouts()
|
val spouts = api.spouts()
|
||||||
if (spouts.success && spouts.data != null) {
|
if (spouts.success && spouts.data != null) {
|
||||||
spouts.data
|
spouts.data
|
||||||
} else {
|
} else {
|
||||||
emptyMap()
|
emptyMap() // TODO: do something here
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw NetworkUnavailableException()
|
throw NetworkUnavailableException()
|
||||||
@ -199,6 +206,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||||
val success = markAsReadById(item.id)
|
val success = markAsReadById(item.id)
|
||||||
|
|
||||||
@ -217,6 +225,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||||
val success = unmarkAsReadById(item.id)
|
val success = unmarkAsReadById(item.id)
|
||||||
|
|
||||||
@ -235,6 +244,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||||
val success = starrById(item.id)
|
val success = starrById(item.id)
|
||||||
|
|
||||||
@ -253,6 +263,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||||
val success = unstarrById(item.id)
|
val success = unstarrById(item.id)
|
||||||
|
|
||||||
@ -271,6 +282,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
|
|
||||||
@ -530,6 +542,7 @@ class Repository(
|
|||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add tests
|
||||||
suspend fun handleDBActions() {
|
suspend fun handleDBActions() {
|
||||||
|
|
||||||
val actions: List<ACTION> = getDBActions()
|
val actions: List<ACTION> = getDBActions()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
import io.github.aakira.napier.Napier
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
|
||||||
class AppSettingsService {
|
class AppSettingsService {
|
||||||
val settings: Settings = Settings()
|
val settings: Settings = Settings()
|
||||||
|
Loading…
Reference in New Issue
Block a user