This commit is contained in:
parent
e0c118a73e
commit
95e76a55da
@ -366,7 +366,6 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -539,11 +538,9 @@ 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,7 +27,6 @@ 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 {
|
||||||
@ -157,13 +156,64 @@ 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()
|
||||||
|
|
||||||
var cancel = false
|
failInvalidUrl(url)
|
||||||
var focusView: View? = null
|
failLoginDetails(password, login)
|
||||||
|
|
||||||
|
showProgress(true)
|
||||||
|
|
||||||
|
repository.refreshLoginInformation(url, login, password)
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
val result = repository.login()
|
||||||
|
if (result) {
|
||||||
|
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
|
||||||
|
if (!errorFetching && !displaySelfossOnly) {
|
||||||
|
goToMain()
|
||||||
|
} else {
|
||||||
|
if (displaySelfossOnly) {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
R.string.application_selfoss_only,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
preferenceError()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
preferenceError()
|
||||||
|
}
|
||||||
|
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()) {
|
if (url.isBaseUrlInvalid()) {
|
||||||
binding.urlView.error = getString(R.string.login_url_problem)
|
|
||||||
focusView = binding.urlView
|
|
||||||
cancel = true
|
cancel = true
|
||||||
|
binding.urlView.error = getString(R.string.login_url_problem)
|
||||||
inValidCount++
|
inValidCount++
|
||||||
if (inValidCount == 3) {
|
if (inValidCount == 3) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
@ -177,49 +227,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
inValidCount = 0
|
inValidCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
maybeCancelAndFocusView(cancel, focusView)
|
||||||
|
}
|
||||||
|
|
||||||
if (isWithLogin) {
|
private fun maybeCancelAndFocusView(cancel: Boolean, focusView: View?) {
|
||||||
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) {
|
if (cancel) {
|
||||||
focusView?.requestFocus()
|
focusView?.requestFocus()
|
||||||
} else {
|
|
||||||
showProgress(true)
|
|
||||||
|
|
||||||
repository.refreshLoginInformation(url, login, password)
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
val result = repository.login()
|
|
||||||
if (result) {
|
|
||||||
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
|
|
||||||
if (!errorFetching && !displaySelfossOnly) {
|
|
||||||
goToMain()
|
|
||||||
} else {
|
|
||||||
if (displaySelfossOnly) {
|
|
||||||
Toast.makeText(
|
|
||||||
applicationContext,
|
|
||||||
R.string.application_selfoss_only,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
preferenceError()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
preferenceError()
|
|
||||||
}
|
|
||||||
showProgress(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,14 @@ 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 { Repository(instance(), instance(), isConnectionAvailable, instance()) }
|
bind<Repository>() with singleton {
|
||||||
|
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()) }
|
||||||
}
|
}
|
||||||
@ -59,7 +66,12 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
|
|
||||||
handleNotificationChannels()
|
handleNotificationChannels()
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
|
ProcessLifecycleOwner.get().lifecycle.addObserver(
|
||||||
|
AppLifeCycleObserver(
|
||||||
|
connectivityStatus,
|
||||||
|
repository
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
||||||
@ -88,20 +100,36 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
initAcra {
|
initAcra {
|
||||||
reportFormat = StringFormat.JSON
|
reportFormat = StringFormat.JSON
|
||||||
reportContent = listOf(
|
reportContent = listOf(
|
||||||
ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
|
ReportField.REPORT_ID,
|
||||||
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
|
ReportField.INSTALLATION_ID,
|
||||||
ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
|
ReportField.APP_VERSION_CODE,
|
||||||
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
|
ReportField.APP_VERSION_NAME,
|
||||||
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
|
ReportField.BUILD,
|
||||||
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
|
ReportField.ANDROID_VERSION,
|
||||||
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
|
ReportField.BRAND,
|
||||||
|
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 = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
|
uri =
|
||||||
|
"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
|
||||||
@ -119,7 +147,11 @@ 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(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
|
val newItemsChannelmChannel = NotificationChannel(
|
||||||
|
AppSettingsService.newItemsChannelId,
|
||||||
|
newItemsChannelname,
|
||||||
|
newItemsChannelimportance
|
||||||
|
)
|
||||||
|
|
||||||
notificationManager.createNotificationChannel(mChannel)
|
notificationManager.createNotificationChannel(mChannel)
|
||||||
notificationManager.createNotificationChannel(newItemsChannelmChannel)
|
notificationManager.createNotificationChannel(newItemsChannelmChannel)
|
||||||
@ -133,13 +165,17 @@ 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(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
|
class AppLifeCycleObserver(
|
||||||
|
val connectivityStatus: ConnectivityStatus,
|
||||||
|
val repository: Repository
|
||||||
|
) : DefaultLifecycleObserver {
|
||||||
|
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
super.onResume(owner)
|
super.onResume(owner)
|
||||||
|
@ -184,13 +184,11 @@ 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,85 +26,89 @@ 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), DIAware {
|
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params),
|
||||||
|
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 {
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
val notification =
|
val notification =
|
||||||
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
|
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
|
||||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||||
.setContentText(context.getString(R.string.loading_notification_text))
|
.setContentText(context.getString(R.string.loading_notification_text))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setPriority(PRIORITY_LOW)
|
.setPriority(PRIORITY_LOW)
|
||||||
.setChannelId(AppSettingsService.syncChannelId)
|
.setChannelId(AppSettingsService.syncChannelId)
|
||||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||||
|
|
||||||
notificationManager.notify(1, notification.build())
|
notificationManager.notify(1, notification.build())
|
||||||
|
|
||||||
repository.handleDBActions()
|
repository.handleDBActions()
|
||||||
|
|
||||||
val apiItems = repository.tryToCacheItemsAndGetNewOnes()
|
val apiItems = repository.tryToCacheItemsAndGetNewOnes()
|
||||||
if (appSettingsService.isNotifyNewItemsEnabled()) {
|
if (appSettingsService.isNotifyNewItemsEnabled()) {
|
||||||
launch {
|
launch {
|
||||||
handleNewItemsNotification(apiItems, notificationManager)
|
handleNewItemsNotification(apiItems, notificationManager)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
apiItems.map { it.preloadImages(context) }
|
||||||
}
|
}
|
||||||
apiItems.map { it.preloadImages(context) }
|
|
||||||
}
|
}
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
val newSize = apiItems.filter { it.unread }.size
|
val newSize = apiItems.filter { it.unread }.size
|
||||||
if (newSize > 0) {
|
if (newSize > 0) {
|
||||||
|
|
||||||
val intent = Intent(context, MainActivity::class.java).apply {
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
}
|
|
||||||
val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
PendingIntent.FLAG_IMMUTABLE
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
|
|
||||||
|
|
||||||
val newItemsNotification =
|
|
||||||
NotificationCompat.Builder(applicationContext, AppSettingsService.newItemsChannelId)
|
|
||||||
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
|
||||||
.setContentText(
|
|
||||||
context.getString(
|
|
||||||
R.string.new_items_notification_text,
|
|
||||||
newSize
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.setPriority(PRIORITY_DEFAULT)
|
|
||||||
.setChannelId(AppSettingsService.newItemsChannelId)
|
|
||||||
.setContentIntent(pendingIntent)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
|
||||||
|
|
||||||
Timer("", false).schedule(4000) {
|
|
||||||
notificationManager.notify(2, newItemsNotification.build())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
val pendingIntent: PendingIntent =
|
||||||
|
PendingIntent.getActivity(context, 0, intent, pflags)
|
||||||
|
|
||||||
|
val newItemsNotification =
|
||||||
|
NotificationCompat.Builder(
|
||||||
|
applicationContext,
|
||||||
|
AppSettingsService.newItemsChannelId
|
||||||
|
)
|
||||||
|
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||||
|
.setContentText(
|
||||||
|
context.getString(
|
||||||
|
R.string.new_items_notification_text,
|
||||||
|
newSize
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setPriority(PRIORITY_DEFAULT)
|
||||||
|
.setChannelId(AppSettingsService.newItemsChannelId)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||||
|
|
||||||
|
Timer("", false).schedule(4000) {
|
||||||
|
notificationManager.notify(2, newItemsNotification.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
Timer("", false).schedule(4000) {
|
Timer("", false).schedule(4000) {
|
||||||
notificationManager.cancel(1)
|
notificationManager.cancel(1)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,9 @@ 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
|
||||||
@ -55,6 +57,8 @@ 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
|
||||||
@ -63,12 +67,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()
|
||||||
|
|
||||||
@ -77,7 +81,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?) {
|
||||||
@ -115,48 +119,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
|
|
||||||
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
||||||
|
|
||||||
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
|
||||||
floatingToolbar.attachFab(fab)
|
|
||||||
|
|
||||||
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
|
|
||||||
|
|
||||||
floatingToolbar.setClickListener(
|
|
||||||
object : FloatingToolbar.ItemClickListener {
|
|
||||||
override fun onItemClick(item: MenuItem) {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
|
||||||
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
|
|
||||||
R.id.unread_action -> if (context != null) {
|
|
||||||
if (this@ArticleFragment.item.unread) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
repository.markAsRead(this@ArticleFragment.item)
|
|
||||||
}
|
|
||||||
this@ArticleFragment.item.unread = false
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
R.string.marked_as_read,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
|
||||||
}
|
|
||||||
this@ArticleFragment.item.unread = true
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
R.string.marked_as_unread,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemLongClick(item: MenuItem?) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (staticBar) {
|
if (staticBar) {
|
||||||
fab.hide()
|
fab.hide()
|
||||||
@ -168,28 +131,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
binding.source.typeface = typeface
|
binding.source.typeface = typeface
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentText.isEmptyOrNullOrNullString()) {
|
handleContent()
|
||||||
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(
|
binding.nestedScrollView.setOnScrollChangeListener(
|
||||||
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
@ -211,7 +153,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
.setPositiveButton(android.R.string.ok
|
.setPositiveButton(
|
||||||
|
android.R.string.ok
|
||||||
) { _, _ ->
|
) { _, _ ->
|
||||||
appSettingsService.disableArticleViewer()
|
appSettingsService.disableArticleViewer()
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
@ -223,6 +166,80 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
return binding.root
|
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
|
||||||
|
floatingToolbar.attachFab(fab)
|
||||||
|
|
||||||
|
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
|
||||||
|
|
||||||
|
floatingToolbar.setClickListener(
|
||||||
|
object : FloatingToolbar.ItemClickListener {
|
||||||
|
override fun onItemClick(item: MenuItem) {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||||
|
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
|
R.id.unread_action -> if (context != null) {
|
||||||
|
if (this@ArticleFragment.item.unread) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
|
}
|
||||||
|
this@ArticleFragment.item.unread = false
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.marked_as_read,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||||
|
}
|
||||||
|
this@ArticleFragment.item.unread = true
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.marked_as_unread,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemLongClick(item: MenuItem?) {
|
||||||
|
// We do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return floatingToolbar
|
||||||
|
}
|
||||||
|
|
||||||
private fun refreshAlignment() {
|
private fun refreshAlignment() {
|
||||||
textAlignment = when (appSettingsService.getActiveAllignment()) {
|
textAlignment = when (appSettingsService.getActiveAllignment()) {
|
||||||
1 -> "justify"
|
1 -> "justify"
|
||||||
@ -232,75 +249,108 @@ 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 {
|
||||||
try {
|
try {
|
||||||
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
|
|
||||||
} catch (e: MalformedURLException) {
|
|
||||||
// Mercury returned a relative url
|
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > malformedurlexception")
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
contentText = response.data!!.content.orEmpty()
|
|
||||||
htmlToWebview()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > contenttext or html")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
|
|
||||||
try {
|
|
||||||
binding.imageView.visibility = View.VISIBLE
|
|
||||||
try {
|
|
||||||
Glide
|
|
||||||
.with(requireContext())
|
|
||||||
.asBitmap()
|
|
||||||
.load(
|
|
||||||
response.data!!.lead_image_url.orEmpty()
|
|
||||||
)
|
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(binding.imageView)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > glide lead image")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > outside glide lead image")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
binding.imageView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
binding.nestedScrollView.scrollTo(0, 0)
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > scrollview")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
openInBrowserAfterFailing()
|
|
||||||
}
|
}
|
||||||
} catch (e: SocketTimeoutException) {
|
URL(response.data!!.url)
|
||||||
openInBrowserAfterFailing()
|
url = response.data!!.url
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > whole thing")
|
contentText = response.data!!.content.orEmpty()
|
||||||
|
htmlToWebview()
|
||||||
|
|
||||||
|
handleLeadImage(response)
|
||||||
|
|
||||||
|
binding.nestedScrollView.scrollTo(0, 0)
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
} else {
|
||||||
openInBrowserAfterFailing()
|
openInBrowserAfterFailing()
|
||||||
}
|
}
|
||||||
|
} catch (e: SocketTimeoutException) {
|
||||||
|
openInBrowserAfterFailing()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
|
||||||
|
openInBrowserAfterFailing()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleLeadImage(response: StatusAndData<MercuryModel.ParsedContent>) {
|
||||||
|
if (!response.data?.lead_image_url.isNullOrEmpty() && context != null) {
|
||||||
|
binding.imageView.visibility = View.VISIBLE
|
||||||
|
Glide
|
||||||
|
.with(requireContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(
|
||||||
|
response.data!!.lead_image_url.orEmpty()
|
||||||
|
)
|
||||||
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(binding.imageView)
|
||||||
|
} else {
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -324,57 +374,19 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
binding.webcontent.settings.loadWithOverviewMode = true
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
binding.webcontent.settings.javaScriptEnabled = false
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
|
|
||||||
binding.webcontent.webViewClient = object : WebViewClient() {
|
handleImageLoading()
|
||||||
@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")
|
val gestureDetector =
|
||||||
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
|
GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
|
return performClick()
|
||||||
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)
|
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
|
||||||
return performClick()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
|
|
||||||
|
|
||||||
binding.webcontent.settings.layoutAlgorithm =
|
binding.webcontent.settings.layoutAlgorithm =
|
||||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
|
|
||||||
var baseUrl: String? = null
|
var baseUrl: String? = null
|
||||||
|
|
||||||
@ -385,7 +397,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
|
e.sendSilentlyWithAcraWithName("htmlToWebview > item url")
|
||||||
}
|
}
|
||||||
|
|
||||||
val fontName = when (font) {
|
val fontName = when (font) {
|
||||||
getString(R.string.open_sans_font_id) -> "Open Sans"
|
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||||
getString(R.string.roboto_font_id) -> "Roboto"
|
getString(R.string.roboto_font_id) -> "Roboto"
|
||||||
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||||
@ -393,7 +405,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
val fontLinkAndStyle = if (font.isNotEmpty()) {
|
||||||
"""<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet">
|
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||||
|
fontName.replace(
|
||||||
|
" ",
|
||||||
|
"+"
|
||||||
|
)
|
||||||
|
}" rel="stylesheet">
|
||||||
|<style>
|
|<style>
|
||||||
| * {
|
| * {
|
||||||
| font-family: '$fontName';
|
| font-family: '$fontName';
|
||||||
@ -417,7 +434,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| max-width: 100%;
|
| max-width: 100%;
|
||||||
| }
|
| }
|
||||||
| a {
|
| a {
|
||||||
| color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
|
| color: ${
|
||||||
|
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)};
|
||||||
@ -428,11 +450,26 @@ 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: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
|
| background-color: ${
|
||||||
|
String.format(
|
||||||
|
"#%06X",
|
||||||
|
0xFFFFFF and colorSurface.data
|
||||||
|
)
|
||||||
|
};
|
||||||
| }
|
| }
|
||||||
| body, html {
|
| body, html {
|
||||||
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
|
| background-color: ${
|
||||||
| border-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
|
String.format(
|
||||||
|
"#%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;
|
||||||
| }
|
| }
|
||||||
@ -442,7 +479,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| pre, code {
|
| pre, code {
|
||||||
| white-space: pre-wrap;
|
| white-space: pre-wrap;
|
||||||
| width:100%;
|
| width:100%;
|
||||||
| background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
|
| background-color: ${
|
||||||
|
String.format(
|
||||||
|
"#%06X",
|
||||||
|
0xFFFFFF and colorSurface.data
|
||||||
|
)
|
||||||
|
};
|
||||||
| }
|
| }
|
||||||
| </style>
|
| </style>
|
||||||
| $fontLinkAndStyle
|
| $fontLinkAndStyle
|
||||||
@ -458,12 +500,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() {
|
||||||
@ -475,7 +517,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
private const val ARG_ITEMS = "items"
|
private const val ARG_ITEMS = "items"
|
||||||
|
|
||||||
fun newInstance(
|
fun newInstance(
|
||||||
item: SelfossModel.Item
|
item: SelfossModel.Item
|
||||||
): ArticleFragment {
|
): ArticleFragment {
|
||||||
val fragment = ArticleFragment()
|
val fragment = ArticleFragment()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
@ -487,9 +529,10 @@ 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)
|
||||||
|
@ -13,6 +13,7 @@ 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
|
||||||
@ -35,6 +36,7 @@ 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()
|
||||||
|
|
||||||
@ -45,8 +47,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val binding =
|
binding =
|
||||||
bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate(
|
FilterFragmentBinding.inflate(
|
||||||
inflater,
|
inflater,
|
||||||
container,
|
container,
|
||||||
false
|
false
|
||||||
@ -54,126 +56,14 @@ 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 {
|
||||||
val tags = repository.getTags()
|
handleTagChips(context)
|
||||||
|
handleSourceChips(context)
|
||||||
tags.forEach { tag ->
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
repository.getSources().forEach { source ->
|
|
||||||
val c = Chip(context)
|
|
||||||
|
|
||||||
Glide.with(context)
|
|
||||||
.load(source.getIcon(repository.baseUrl))
|
|
||||||
.listener(object : RequestListener<Drawable?> {
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable?>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable?>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
try {
|
|
||||||
c.chipIcon = resource
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}).preload()
|
|
||||||
|
|
||||||
c.text = source.title.getHtmlDecoded()
|
|
||||||
|
|
||||||
c.setOnCloseIconClickListener {
|
|
||||||
(it as Chip).isCloseIconVisible = false
|
|
||||||
selectedChip = null
|
|
||||||
repository.setSourceFilter(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.setOnClickListener {
|
|
||||||
if (selectedChip != null) {
|
|
||||||
selectedChip!!.isCloseIconVisible = false
|
|
||||||
}
|
|
||||||
(it as Chip).isCloseIconVisible = true
|
|
||||||
selectedChip = it
|
|
||||||
repository.setSourceFilter(source)
|
|
||||||
|
|
||||||
repository.setTagFilter(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (repository.sourceFilter.value?.equals(source) == true) {
|
|
||||||
c.isCloseIconVisible = true
|
|
||||||
selectedChip = c
|
|
||||||
}
|
|
||||||
|
|
||||||
c.isEnabled = source.error.isBlank()
|
|
||||||
|
|
||||||
if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
c.tooltipText = source.error
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceGroup.addView(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.progressBar2.visibility = GONE
|
binding.progressBar2.visibility = GONE
|
||||||
binding.filterView.visibility = VISIBLE
|
binding.filterView.visibility = VISIBLE
|
||||||
@ -188,6 +78,131 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun handleSourceChips(
|
||||||
|
context: Context
|
||||||
|
) {
|
||||||
|
val sourceGroup = binding.sourcesGroup
|
||||||
|
|
||||||
|
repository.getSources().forEach { source ->
|
||||||
|
val c = Chip(context)
|
||||||
|
|
||||||
|
Glide.with(context)
|
||||||
|
.load(source.getIcon(repository.baseUrl))
|
||||||
|
.listener(object : RequestListener<Drawable?> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable?>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Drawable?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable?>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
try {
|
||||||
|
c.chipIcon = resource
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}).preload()
|
||||||
|
|
||||||
|
c.text = source.title.getHtmlDecoded()
|
||||||
|
|
||||||
|
c.setOnCloseIconClickListener {
|
||||||
|
(it as Chip).isCloseIconVisible = false
|
||||||
|
selectedChip = null
|
||||||
|
repository.setSourceFilter(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.setOnClickListener {
|
||||||
|
if (selectedChip != null) {
|
||||||
|
selectedChip!!.isCloseIconVisible = false
|
||||||
|
}
|
||||||
|
(it as Chip).isCloseIconVisible = true
|
||||||
|
selectedChip = it
|
||||||
|
repository.setSourceFilter(source)
|
||||||
|
|
||||||
|
repository.setTagFilter(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (repository.sourceFilter.value?.equals(source) == true) {
|
||||||
|
c.isCloseIconVisible = true
|
||||||
|
selectedChip = c
|
||||||
|
}
|
||||||
|
|
||||||
|
c.isEnabled = source.error.isBlank()
|
||||||
|
|
||||||
|
if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
c.tooltipText = source.error
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceGroup.addView(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleTagChips(
|
||||||
|
context: Context,
|
||||||
|
) {
|
||||||
|
val tagGroup = binding.tagsGroup
|
||||||
|
|
||||||
|
val tags = repository.getTags()
|
||||||
|
|
||||||
|
tags.forEach { tag ->
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "FilterModalBottomSheet"
|
const val TAG = "FilterModalBottomSheet"
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ 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,7 +21,6 @@ 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"
|
||||||
|
|
||||||
@ -146,8 +145,12 @@ 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) {
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
// We do nothing
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
@ -18,6 +18,18 @@ 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>()
|
||||||
@ -45,7 +57,7 @@ class RepositoryTest {
|
|||||||
fun setup() {
|
fun setup() {
|
||||||
clearAllMocks()
|
clearAllMocks()
|
||||||
every { appSettingsService.getApiVersion() } returns 4
|
every { appSettingsService.getApiVersion() } returns 4
|
||||||
every { appSettingsService.getBaseUrl() } returns "https://test.com/selfoss/"
|
every { appSettingsService.getBaseUrl() } returns BASE_URL
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
|
|
||||||
@ -229,9 +241,9 @@ class RepositoryTest {
|
|||||||
1,
|
1,
|
||||||
"Test",
|
"Test",
|
||||||
listOf("tags"),
|
listOf("tags"),
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
|
IMAGE_URL,
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
))
|
))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@ -550,18 +562,18 @@ class RepositoryTest {
|
|||||||
1,
|
1,
|
||||||
"First source",
|
"First source",
|
||||||
listOf("Test", "second"),
|
listOf("Test", "second"),
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
"d8c92cdb1ef119ea85c4b9205c879ca7.png",
|
IMAGE_URL_2,
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
),
|
),
|
||||||
SelfossModel.Source(
|
SelfossModel.Source(
|
||||||
2,
|
2,
|
||||||
"Second source",
|
"Second source",
|
||||||
listOf("second"),
|
listOf("second"),
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
|
IMAGE_URL,
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -570,18 +582,18 @@ class RepositoryTest {
|
|||||||
"1",
|
"1",
|
||||||
"First DB source",
|
"First DB source",
|
||||||
"Test,second",
|
"Test,second",
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
"d8c92cdb1ef119ea85c4b9205c879ca7.png",
|
IMAGE_URL_2,
|
||||||
"url"
|
"url"
|
||||||
),
|
),
|
||||||
SOURCE(
|
SOURCE(
|
||||||
"2",
|
"2",
|
||||||
"Second source",
|
"Second source",
|
||||||
"second",
|
"second",
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
|
IMAGE_URL,
|
||||||
"url"
|
"url"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -720,9 +732,9 @@ class RepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.createSource(
|
response = repository.createSource(
|
||||||
"test",
|
"test",
|
||||||
"https://test.com/feed",
|
FEED_URL,
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"Test, New",
|
TAGS,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,9 +759,9 @@ class RepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.createSource(
|
response = repository.createSource(
|
||||||
"test",
|
"test",
|
||||||
"https://test.com/feed",
|
FEED_URL,
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"Test, New"
|
TAGS
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,9 +786,9 @@ class RepositoryTest {
|
|||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.createSource(
|
response = repository.createSource(
|
||||||
"test",
|
"test",
|
||||||
"https://test.com/feed",
|
FEED_URL,
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"Test, New"
|
TAGS
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,12 +964,12 @@ class RepositoryTest {
|
|||||||
coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
|
coEvery { appSettingsService.refreshLoginInformation(any(), any(), any()) } returns Unit
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.refreshLoginInformation("https://test.com/selfoss/", "login", "password")
|
repository.refreshLoginInformation(BASE_URL, "login", "password")
|
||||||
|
|
||||||
coVerify(exactly = 1) { api.refreshLoginInformation() }
|
coVerify(exactly = 1) { api.refreshLoginInformation() }
|
||||||
coVerify(exactly = 1) {
|
coVerify(exactly = 1) {
|
||||||
appSettingsService.refreshLoginInformation(
|
appSettingsService.refreshLoginInformation(
|
||||||
"https://test.com/selfoss/",
|
BASE_URL,
|
||||||
"login",
|
"login",
|
||||||
"password"
|
"password"
|
||||||
)
|
)
|
||||||
@ -1031,9 +1043,9 @@ class RepositoryTest {
|
|||||||
1,
|
1,
|
||||||
"First source",
|
"First source",
|
||||||
listOf("Test", "second"),
|
listOf("Test", "second"),
|
||||||
"spouts\\rss\\fulltextrss",
|
SPOUT,
|
||||||
"",
|
"",
|
||||||
"d8c92cdb1ef119ea85c4b9205c879ca7.png",
|
IMAGE_URL_2,
|
||||||
SelfossModel.SourceParams("url")
|
SelfossModel.SourceParams("url")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -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?,
|
val lead_image_url: String?, // NOSONAR
|
||||||
val url: String
|
val url: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,19 +81,13 @@ 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 =
|
stringUrl = if (link.contains("//news.google.com/news/") && link.contains("&url=")) {
|
||||||
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
link.substringAfter("&url=")
|
||||||
if (link.contains("&url=")) {
|
} else {
|
||||||
link.substringAfter("&url=")
|
this.link.replace("&", "&")
|
||||||
} else {
|
}
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.link.replace("&", "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle :443 => https
|
// handle :443 => https
|
||||||
if (stringUrl.contains(":443")) {
|
if (stringUrl.contains(":443")) {
|
||||||
|
@ -51,9 +51,7 @@ 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,
|
||||||
@ -63,31 +61,27 @@ class Repository(
|
|||||||
searchFilter,
|
searchFilter,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else {
|
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||||
if (appSettingsService.isItemCachingEnabled()) {
|
var dbItems = getDBItems().filter {
|
||||||
fromDB = true
|
displayedItems == ItemType.ALL ||
|
||||||
var dbItems = getDBItems().filter {
|
(it.unread && displayedItems == ItemType.UNREAD) ||
|
||||||
displayedItems == ItemType.ALL ||
|
(it.starred && displayedItems == ItemType.STARRED)
|
||||||
(it.unread && displayedItems == ItemType.UNREAD) ||
|
|
||||||
(it.starred && displayedItems == ItemType.STARRED)
|
|
||||||
}
|
|
||||||
if (tagFilter.value != null) {
|
|
||||||
dbItems = dbItems.filter { it.tags.split(',').contains(tagFilter.value!!.tag) }
|
|
||||||
}
|
|
||||||
if (sourceFilter.value != null) {
|
|
||||||
dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
|
|
||||||
}
|
|
||||||
fetchedItems = StatusAndData.succes(
|
|
||||||
dbItems.map { it.toView() }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
if (tagFilter.value != null) {
|
||||||
|
dbItems = dbItems.filter { it.tags.split(',').contains(tagFilter.value!!.tag) }
|
||||||
|
}
|
||||||
|
if (sourceFilter.value != null) {
|
||||||
|
dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
|
||||||
|
}
|
||||||
|
val itemsList = ArrayList(dbItems.map { it.toView() })
|
||||||
|
itemsList.sortByDescending { DateUtils.parseDate(it.datetime) }
|
||||||
|
fetchedItems = StatusAndData.succes(
|
||||||
|
itemsList
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@ -173,14 +167,13 @@ 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() // TODO: do something here
|
emptyMap()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw NetworkUnavailableException()
|
throw NetworkUnavailableException()
|
||||||
@ -206,7 +199,6 @@ 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)
|
||||||
|
|
||||||
@ -225,7 +217,6 @@ 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)
|
||||||
|
|
||||||
@ -244,7 +235,6 @@ 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)
|
||||||
|
|
||||||
@ -263,7 +253,6 @@ 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)
|
||||||
|
|
||||||
@ -282,7 +271,6 @@ 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
|
||||||
|
|
||||||
@ -542,7 +530,6 @@ 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,8 +1,6 @@
|
|||||||
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