Compare commits

..

18 Commits

Author SHA1 Message Date
1c57435f54 Store api version and select correct date formatter when offline. 2021-03-21 14:16:21 +01:00
4bb20a75d7 Move api check to MyApp. 2021-03-21 13:57:08 +01:00
1f20e19a97 Ensure api version has been fetched before checking. 2021-03-21 11:45:25 +01:00
9b372a45ce Global date formatter. 2021-03-20 07:58:08 +01:00
f4f8503037 Check new date format in items. 2021-03-19 17:24:25 +01:00
5b70ae138e Set date format according to api version. 2021-03-19 17:23:44 +01:00
626c9e2797 Migrate to View Binding. (#338) 2021-03-17 17:50:44 +01:00
05cd96afc0 New Crowdin updates (#333)
* New translations strings.xml (Sinhala)

* New translations strings.xml (Sinhala)

* New translations strings.xml (French)

* New translations strings.xml (Spanish)

* New translations strings.xml (Catalan)

* New translations strings.xml (German)

* New translations strings.xml (Italian)

* New translations strings.xml (Korean)

* New translations strings.xml (Dutch)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Turkish)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Galician)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Persian)

* New translations strings.xml (Sinhala)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)
2021-03-14 09:10:25 +01:00
c8faa8984f Load settings when the home activity is created. (#336) 2021-03-14 09:09:12 +01:00
a025efbf3b Fetch articles only when the home activity is created. (#335) 2021-03-11 22:04:10 +01:00
e62e04e13b Added option to prevent updating sources and tags. (#332)
* Added option to prevent updating sources and tags.

* Delete removed sources and tags from database.
2021-03-06 13:03:45 +01:00
e6b5ea4e67 Download read and starred items (#331)
* Save read and starred articles during background sync

* Use getItems

* Cache images of read articles

* Remove unused function

* Refactor functions
2021-01-12 03:55:47 +01:00
c3148c6744 Sort articles by date when loading from db (#330) 2021-01-11 22:04:25 +01:00
193f538d29 Trying fastlane for f-droid repository. Fixes #321. 2021-01-10 21:40:17 +01:00
7f45db0473 Bakcground image caching issues should be fixed. 2021-01-10 21:20:33 +01:00
d89423b9ac Fix crash when loading image that doesn't exist (#329)
* Wait until the image is downloaded

* Use timeout
2021-01-10 21:14:23 +01:00
25fd869c01 Closes #322. 2021-01-10 12:13:19 +01:00
d1d956b77a Improve image caching (#327)
* Update to support rebase

* Prevent Glide from opening svg images
2021-01-10 05:08:40 +01:00
46 changed files with 638 additions and 391 deletions

View File

@ -36,6 +36,12 @@
- Closing #178. Expending images on tap. - Closing #178. Expending images on tap.
- Closing #323. Old issue with textview not having the right color.
- Closing #324. Svg images loading crashes the app.
- Closing #322. App crashed because of svg images.
**1.6.x** **1.6.x**
- Handling hidden tags. - Handling hidden tags.
@ -56,6 +62,8 @@
- Fixes #215, #208. - Fixes #215, #208.
- Fixes #328.
**1.5.7.x** **1.5.7.x**
- Added confirmation to the mark as read and update menues. - Added confirmation to the mark as read and update menues.

View File

@ -30,8 +30,6 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
android { android {
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
@ -39,6 +37,9 @@ android {
} }
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.3' buildToolsVersion '30.0.3'
buildFeatures {
viewBinding true
}
defaultConfig { defaultConfig {
applicationId "apps.amine.bou.readerforselfoss" applicationId "apps.amine.bou.readerforselfoss"
minSdkVersion 16 minSdkVersion 16

View File

@ -23,11 +23,11 @@ import apps.amine.bou.readerforselfoss.themes.Toppings
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_add_source.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import android.graphics.PorterDuff import android.graphics.PorterDuff
import apps.amine.bou.readerforselfoss.databinding.ActivityAddSourceBinding
@ -37,50 +37,53 @@ class AddSourceActivity : AppCompatActivity() {
private lateinit var api: SelfossApi private lateinit var api: SelfossApi
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
private lateinit var binding: ActivityAddSourceBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@AddSourceActivity) appColors = AppColors(this@AddSourceActivity)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityAddSourceBinding.inflate(layoutInflater)
val view = binding.root
setContentView(R.layout.activity_add_source) setContentView(view)
val scoop = Scoop.getInstance() val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, toolbar) scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
} }
val drawable = nameInput.background val drawable = binding.nameInput.background
drawable.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP) drawable.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
// TODO: clean // TODO: clean
if(Build.VERSION.SDK_INT > 16) { if(Build.VERSION.SDK_INT > 16) {
nameInput.background = drawable binding.nameInput.background = drawable
} else{ } else{
nameInput.setBackgroundDrawable(drawable) binding.nameInput.setBackgroundDrawable(drawable)
} }
val drawable1 = sourceUri.background val drawable1 = binding.sourceUri.background
drawable1.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP) drawable1.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
if(Build.VERSION.SDK_INT > 16) { if(Build.VERSION.SDK_INT > 16) {
sourceUri.background = drawable1 binding.sourceUri.background = drawable1
} else{ } else{
sourceUri.setBackgroundDrawable(drawable1) binding.sourceUri.setBackgroundDrawable(drawable1)
} }
val drawable2 = tags.background val drawable2 = binding.tags.background
drawable2.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP) drawable2.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
if(Build.VERSION.SDK_INT > 16) { if(Build.VERSION.SDK_INT > 16) {
tags.background = drawable2 binding.tags.background = drawable2
} else{ } else{
tags.setBackgroundDrawable(drawable2) binding.tags.setBackgroundDrawable(drawable2)
} }
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
@ -98,12 +101,12 @@ class AddSourceActivity : AppCompatActivity() {
mustLoginToAddSource() mustLoginToAddSource()
} }
maybeGetDetailsFromIntentSharing(intent, sourceUri, nameInput) maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
saveBtn.setTextColor(appColors.colorAccent) binding.saveBtn.setTextColor(appColors.colorAccent)
saveBtn.setOnClickListener { binding.saveBtn.setOnClickListener {
handleSaveSource(tags, nameInput.text.toString(), sourceUri.text.toString(), api!!) handleSaveSource(binding.tags, binding.nameInput.text.toString(), binding.sourceUri.text.toString(), api)
} }
} }
@ -114,7 +117,7 @@ class AddSourceActivity : AppCompatActivity() {
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) { if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
mustLoginToAddSource() mustLoginToAddSource()
} else { } else {
handleSpoutsSpinner(spoutsSpinner, api, progress, formContainer) handleSpoutsSpinner(binding.spoutsSpinner, api, binding.progress, binding.formContainer)
} }
} }

View File

@ -29,6 +29,7 @@ import apps.amine.bou.readerforselfoss.adapters.ItemListAdapter
import apps.amine.bou.readerforselfoss.adapters.ItemsAdapter import apps.amine.bou.readerforselfoss.adapters.ItemsAdapter
import apps.amine.bou.readerforselfoss.api.selfoss.* import apps.amine.bou.readerforselfoss.api.selfoss.*
import apps.amine.bou.readerforselfoss.background.LoadingWorker import apps.amine.bou.readerforselfoss.background.LoadingWorker
import apps.amine.bou.readerforselfoss.databinding.ActivityHomeBinding
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
@ -64,10 +65,10 @@ import com.mikepenz.materialdrawer.holder.StringHolder
import com.mikepenz.materialdrawer.model.DividerDrawerItem import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import kotlinx.android.synthetic.main.activity_home.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
import java.text.SimpleDateFormat
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -101,6 +102,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var infiniteScroll: Boolean = false private var infiniteScroll: Boolean = false
private var lastFetchDone: Boolean = false private var lastFetchDone: Boolean = false
private var itemsCaching: Boolean = false private var itemsCaching: Boolean = false
private var updateSources: Boolean = true
private var hiddenTags: List<String> = emptyList() private var hiddenTags: List<String> = emptyList()
private var periodicRefresh = false private var periodicRefresh = false
@ -120,6 +122,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var firstVisible: Int = 0 private var firstVisible: Int = 0
private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
private lateinit var settings: SharedPreferences private lateinit var settings: SharedPreferences
private lateinit var binding: ActivityHomeBinding
private var recyclerAdapter: RecyclerView.Adapter<*>? = null private var recyclerAdapter: RecyclerView.Adapter<*>? = null
@ -148,6 +151,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
config = Config(this@HomeActivity) config = Config(this@HomeActivity)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
val view = binding.root
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1 fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
offlineShortcut = intent.getBooleanExtra("startOffline", false) offlineShortcut = intent.getBooleanExtra("startOffline", false)
@ -156,11 +161,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
elementsShown = intent.getIntExtra("shortcutTab", UNREAD_SHOWN) elementsShown = intent.getIntExtra("shortcutTab", UNREAD_SHOWN)
} }
setContentView(R.layout.activity_home) setContentView(view)
handleThemeBinding() handleThemeBinding()
setSupportActionBar(toolBar) setSupportActionBar(binding.toolBar)
db = Room.databaseBuilder( db = Room.databaseBuilder(
applicationContext, applicationContext,
@ -186,15 +191,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleDrawer() handleDrawer()
handleSwipeRefreshLayout() handleSwipeRefreshLayout()
handleSharedPrefs()
getElementsAccordingToTab()
} }
private fun handleSwipeRefreshLayout() { private fun handleSwipeRefreshLayout() {
swipeRefreshLayout.setColorSchemeResources( binding.swipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_1, R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_2,
R.color.refresh_progress_3 R.color.refresh_progress_3
) )
swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
offlineShortcut = false offlineShortcut = false
allItems = ArrayList() allItems = ArrayList()
lastFetchDone = false lastFetchDone = false
@ -231,7 +240,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
val i = items.elementAtOrNull(position) val i = items.elementAtOrNull(position)
if (i != null) { if (i != null) {
val adapter = recyclerView.adapter as ItemsAdapter<*> val adapter = binding.recyclerView.adapter as ItemsAdapter<*>
val wasItemUnread = adapter.unreadItemStatusAtIndex(position) val wasItemUnread = adapter.unreadItemStatusAtIndex(position)
@ -269,7 +278,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(recyclerView) ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
} }
private fun handleBottomBar() { private fun handleBottomBar() {
@ -306,18 +315,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
).setActiveColorResource(R.color.pink) ).setActiveColorResource(R.color.pink)
.setBadgeItem(tabStarredBadge) .setBadgeItem(tabStarredBadge)
bottomBar binding.bottomBar
.addItem(tabNew) .addItem(tabNew)
.addItem(tabArchive) .addItem(tabArchive)
.addItem(tabStarred) .addItem(tabStarred)
.setFirstSelectedPosition(0) .setFirstSelectedPosition(0)
.initialise() .initialise()
binding.bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING)
bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING) binding.bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
if (fromTabShortcut) { if (fromTabShortcut) {
bottomBar.selectTab(elementsShown - 1) binding.bottomBar.selectTab(elementsShown - 1)
} }
} }
@ -331,8 +339,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
editor = settings.edit() editor = settings.edit()
handleSharedPrefs()
handleDrawerItems() handleDrawerItems()
handleThemeUpdate() handleThemeUpdate()
@ -340,22 +346,20 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
reloadLayoutManager() reloadLayoutManager()
if (!infiniteScroll) { if (!infiniteScroll) {
recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
} else { } else {
handleInfiniteScroll() handleInfiniteScroll()
} }
handleBottomBarActions() handleBottomBarActions()
getElementsAccordingToTab()
handleRecurringTask() handleRecurringTask()
handleOfflineActions() handleOfflineActions()
} }
private fun getAndStoreAllItems() { private fun getAndStoreAllItems() {
api.allItems().enqueue(object : Callback<List<Item>> { api.allNewItems().enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) { override fun onFailure(call: Call<List<Item>>, t: Throwable) {
} }
@ -363,18 +367,48 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
call: Call<List<Item>>, call: Call<List<Item>>,
response: Response<List<Item>> response: Response<List<Item>>
) { ) {
thread { enqueueArticles(response, true)
if (response.body() != null) {
val apiItems = (response.body() as ArrayList<Item>).filter {
maybeTagFilter != null || filter(it.tags.tags)
} as ArrayList<Item>
db.itemsDao().deleteAllItems()
db.itemsDao()
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
}
}
} }
}) })
api.allReadItems().enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
}
override fun onResponse(
call: Call<List<Item>>,
response: Response<List<Item>>
) {
enqueueArticles(response, false)
}
})
api.allStarredItems().enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
}
override fun onResponse(
call: Call<List<Item>>,
response: Response<List<Item>>
) {
enqueueArticles(response, false)
}
})
}
private fun enqueueArticles(response: Response<List<Item>>, clearDatabase: Boolean) {
thread {
if (response.body() != null) {
val apiItems = (response.body() as ArrayList<Item>).filter {
maybeTagFilter != null || filter(it.tags.tags)
} as ArrayList<Item>
if (clearDatabase) {
db.itemsDao().deleteAllItems()
}
db.itemsDao()
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
}
}
} }
override fun onStop() { override fun onStop() {
@ -394,6 +428,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false) displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
infiniteScroll = sharedPref.getBoolean("infinite_loading", false) infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
itemsCaching = sharedPref.getBoolean("items_caching", false) itemsCaching = sharedPref.getBoolean("items_caching", false)
updateSources = sharedPref.getBoolean("update_sources", true)
hiddenTags = if (sharedPref.getString("hidden_tags", "")!!.isNotEmpty()) { hiddenTags = if (sharedPref.getString("hidden_tags", "")!!.isNotEmpty()) {
sharedPref.getString("hidden_tags", "")!!.replace("\\s".toRegex(), "").split(",") sharedPref.getString("hidden_tags", "")!!.replace("\\s".toRegex(), "").split(",")
} else { } else {
@ -410,7 +445,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun handleThemeBinding() { private fun handleThemeBinding() {
val scoop = Scoop.getInstance() val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, toolBar) scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
} }
@ -433,18 +468,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
drawer = drawer { drawer = drawer {
rootViewRes = R.id.drawer_layout rootViewRes = R.id.drawer_layout
toolbar = toolBar toolbar = binding.toolBar
actionBarDrawerToggleEnabled = true actionBarDrawerToggleEnabled = true
actionBarDrawerToggleAnimated = true actionBarDrawerToggleAnimated = true
showOnFirstLaunch = true showOnFirstLaunch = true
onSlide { _, p1 -> onSlide { _, p1 ->
bottomBar.alpha = (1 - p1) binding.bottomBar.alpha = (1 - p1)
} }
onClosed { onClosed {
bottomBar.show() binding.bottomBar.show()
} }
onOpened { onOpened {
bottomBar.hide() binding.bottomBar.hide()
} }
if (displayAccountHeader) { if (displayAccountHeader) {
@ -696,6 +731,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
if (maybeDrawerData.tags != null) { if (maybeDrawerData.tags != null) {
thread { thread {
val tagEntities = maybeDrawerData.tags.map { it.toEntity() } val tagEntities = maybeDrawerData.tags.map { it.toEntity() }
db.drawerDataDao().deleteAllTags()
db.drawerDataDao().insertAllTags(*tagEntities.toTypedArray()) db.drawerDataDao().insertAllTags(*tagEntities.toTypedArray())
} }
} }
@ -703,6 +739,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
thread { thread {
val sourceEntities = val sourceEntities =
maybeDrawerData.sources.map { it.toEntity() } maybeDrawerData.sources.map { it.toEntity() }
db.drawerDataDao().deleteAllSources()
db.drawerDataDao().insertAllSources(*sourceEntities.toTypedArray()) db.drawerDataDao().insertAllSources(*sourceEntities.toTypedArray())
} }
} }
@ -730,7 +767,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
var sources: List<Source>? var sources: List<Source>?
fun sourcesApiCall() { fun sourcesApiCall() {
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && updateSources) {
api.sources.enqueue(object : Callback<List<Source>> { api.sources.enqueue(object : Callback<List<Source>> {
override fun onResponse( override fun onResponse(
call: Call<List<Source>>?, call: Call<List<Source>>?,
@ -753,7 +790,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut)) { if (this@HomeActivity.isNetworkAccessible(null, offlineShortcut) && updateSources) {
api.tags.enqueue(object : Callback<List<Tag>> { api.tags.enqueue(object : Callback<List<Tag>> {
override fun onResponse( override fun onResponse(
call: Call<List<Tag>>, call: Call<List<Tag>>,
@ -787,7 +824,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun reloadLayoutManager() { private fun reloadLayoutManager() {
val currentManager = recyclerView.layoutManager val currentManager = binding.recyclerView.layoutManager
val layoutManager: RecyclerView.LayoutManager val layoutManager: RecyclerView.LayoutManager
// This will only update the layout manager if settings changed // This will only update the layout manager if settings changed
@ -798,7 +835,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
this, this,
calculateNoOfColumns() calculateNoOfColumns()
) )
recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
is GridLayoutManager -> is GridLayoutManager ->
if (shouldBeCardView) { if (shouldBeCardView) {
@ -808,7 +845,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
) )
layoutManager.gapStrategy = layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
else -> else ->
if (currentManager == null) { if (currentManager == null) {
@ -817,7 +854,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
this, this,
calculateNoOfColumns() calculateNoOfColumns()
) )
recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} else { } else {
layoutManager = StaggeredGridLayoutManager( layoutManager = StaggeredGridLayoutManager(
calculateNoOfColumns(), calculateNoOfColumns(),
@ -825,7 +862,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
) )
layoutManager.gapStrategy = layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
} else { } else {
} }
@ -833,11 +870,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
private fun handleBottomBarActions() { private fun handleBottomBarActions() {
bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener { binding.bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabUnselected(position: Int) = Unit override fun onTabUnselected(position: Int) = Unit
override fun onTabReselected(position: Int) { override fun onTabReselected(position: Int) {
val layoutManager = recyclerView.adapter val layoutManager = binding.recyclerView.adapter
when (layoutManager) { when (layoutManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
@ -862,12 +899,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
if (itemsCaching) { if (itemsCaching) {
if (!swipeRefreshLayout.isRefreshing) { if (!binding.swipeRefreshLayout.isRefreshing) {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true } binding.swipeRefreshLayout.post { binding.swipeRefreshLayout.isRefreshing = true }
} }
thread { thread {
val dbItems = db.itemsDao().items().map { it.toView() } val dbItems = db.itemsDao().items().map { it.toView() }.sortedByDescending {
SimpleDateFormat(dateTimeFormatter).parse(it.datetime)
}
runOnUiThread { runOnUiThread {
if (dbItems.isNotEmpty()) { if (dbItems.isNotEmpty()) {
items = when (position) { items = when (position) {
@ -913,7 +952,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerViewScrollListener = object : RecyclerView.OnScrollListener() { recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) {
if (dy > 0) { if (dy > 0) {
val manager = recyclerView.layoutManager val manager = binding.recyclerView.layoutManager
val lastVisibleItem: Int = when (manager) { val lastVisibleItem: Int = when (manager) {
is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions( is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
null null
@ -929,17 +968,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
} }
recyclerView.clearOnScrollListeners() binding.recyclerView.clearOnScrollListeners()
recyclerView.addOnScrollListener(recyclerViewScrollListener) binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
} }
private fun mayBeEmpty() = private fun mayBeEmpty() =
if (items.isEmpty()) { if (items.isEmpty()) {
emptyText.visibility = View.VISIBLE binding.emptyText.visibility = View.VISIBLE
recyclerView.visibility = View.GONE binding.recyclerView.visibility = View.GONE
} else { } else {
emptyText.visibility = View.GONE binding.emptyText.visibility = View.GONE
recyclerView.visibility = View.VISIBLE binding.recyclerView.visibility = View.VISIBLE
} }
private fun getElementsAccordingToTab( private fun getElementsAccordingToTab(
@ -964,12 +1003,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
if (itemsCaching) { if (itemsCaching) {
if (!swipeRefreshLayout.isRefreshing) { if (!binding.swipeRefreshLayout.isRefreshing) {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true } binding.swipeRefreshLayout.post { binding.swipeRefreshLayout.isRefreshing = true }
} }
thread { thread {
val dbItems = db.itemsDao().items().map { it.toView() } val dbItems = db.itemsDao().items().map { it.toView() }.sortedByDescending {
SimpleDateFormat(dateTimeFormatter).parse(it.datetime)
}
runOnUiThread { runOnUiThread {
if (dbItems.isNotEmpty()) { if (dbItems.isNotEmpty()) {
items = when (elementsShown) { items = when (elementsShown) {
@ -1033,11 +1074,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleListResult(appendResults) handleListResult(appendResults)
if (!appendResults) mayBeEmpty() if (!appendResults) mayBeEmpty()
swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
if (!swipeRefreshLayout.isRefreshing) { if (!binding.swipeRefreshLayout.isRefreshing) {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = true } binding.swipeRefreshLayout.post { binding.swipeRefreshLayout.isRefreshing = true }
} }
if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) { if (this@HomeActivity.isNetworkAccessible(this@HomeActivity.findViewById(R.id.coordLayout), offlineShortcut)) {
@ -1051,7 +1092,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
override fun onFailure(call: Call<List<Item>>, t: Throwable) { override fun onFailure(call: Call<List<Item>>, t: Throwable) {
swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
toastMessage, toastMessage,
@ -1060,7 +1101,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
}) })
} else { } else {
swipeRefreshLayout.post { swipeRefreshLayout.isRefreshing = false } binding.swipeRefreshLayout.post { binding.swipeRefreshLayout.isRefreshing = false }
} }
} }
@ -1105,7 +1146,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun handleListResult(appendResults: Boolean = false) { private fun handleListResult(appendResults: Boolean = false) {
if (appendResults) { if (appendResults) {
val oldManager = recyclerView.layoutManager val oldManager = binding.recyclerView.layoutManager
firstVisible = when (oldManager) { firstVisible = when (oldManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPositions(null).last() oldManager.findFirstCompletelyVisibleItemPositions(null).last()
@ -1150,14 +1191,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
updateItems(it) updateItems(it)
} }
recyclerView.addItemDecoration( binding.recyclerView.addItemDecoration(
DividerItemDecoration( DividerItemDecoration(
this@HomeActivity, this@HomeActivity,
DividerItemDecoration.VERTICAL DividerItemDecoration.VERTICAL
) )
) )
} }
recyclerView.adapter = recyclerAdapter binding.recyclerView.adapter = recyclerAdapter
} else { } else {
if (!appendResults) { if (!appendResults) {
(recyclerAdapter as ItemsAdapter<*>).updateAllItems(items) (recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
@ -1308,7 +1349,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
R.id.readAll -> { R.id.readAll -> {
if (elementsShown == UNREAD_SHOWN) { if (elementsShown == UNREAD_SHOWN) {
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) { needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
val ids = allItems.map { it.id } val ids = allItems.map { it.id }
val itemsByTag: Map<Long, Int> = val itemsByTag: Map<Long, Int> =
allItems.flattenTags() allItems.flattenTags()
@ -1343,7 +1384,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { override fun onFailure(call: Call<SuccessResponse>, t: Throwable) {
@ -1352,7 +1393,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
R.string.all_posts_not_read, R.string.all_posts_not_read,
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
}) })
items = ArrayList() items = ArrayList()

View File

@ -5,27 +5,31 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter import androidx.fragment.app.FragmentStatePagerAdapter
import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding
import apps.amine.bou.readerforselfoss.fragments.ImageFragment import apps.amine.bou.readerforselfoss.fragments.ImageFragment
import kotlinx.android.synthetic.main.activity_reader.*
class ImageActivity : AppCompatActivity() { class ImageActivity : AppCompatActivity() {
private lateinit var allImages : ArrayList<String> private lateinit var allImages : ArrayList<String>
private var position : Int = 0 private var position : Int = 0
private lateinit var binding: ActivityImageBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityImageBinding.inflate(layoutInflater)
val view = binding.root
setContentView(R.layout.activity_image) setContentView(view)
setSupportActionBar(toolBar) setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayShowTitleEnabled(false) supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String> allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
position = intent.getIntExtra("position", 0) position = intent.getIntExtra("position", 0)
pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager) binding.pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager)
pager.currentItem = position binding.pager.currentItem = position
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@ -17,13 +17,13 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.databinding.ActivityLoginBinding
import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.android.synthetic.main.activity_login.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -39,15 +39,18 @@ class LoginActivity : AppCompatActivity() {
private lateinit var editor: SharedPreferences.Editor private lateinit var editor: SharedPreferences.Editor
private lateinit var userIdentifier: String private lateinit var userIdentifier: String
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@LoginActivity) appColors = AppColors(this@LoginActivity)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
val view = binding.root
setContentView(R.layout.activity_login) setContentView(view)
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
handleBaseUrlFail() handleBaseUrlFail()
@ -65,14 +68,14 @@ class LoginActivity : AppCompatActivity() {
private fun handleActions() { private fun handleActions() {
withSelfhostedCert.setOnCheckedChangeListener { _, b -> binding.withSelfhostedCert.setOnCheckedChangeListener { _, b ->
isWithSelfSignedCert = !isWithSelfSignedCert isWithSelfSignedCert = !isWithSelfSignedCert
val visi: Int = if (b) View.VISIBLE else View.GONE val visi: Int = if (b) View.VISIBLE else View.GONE
warningText.visibility = visi binding.warningText.visibility = visi
} }
passwordView.setOnEditorActionListener( binding.passwordView.setOnEditorActionListener(
TextView.OnEditorActionListener { _, id, _ -> TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.loginView || id == EditorInfo.IME_NULL) { if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
attemptLogin() attemptLogin()
@ -82,22 +85,22 @@ class LoginActivity : AppCompatActivity() {
} }
) )
signInButton.setOnClickListener { attemptLogin() } binding.signInButton.setOnClickListener { attemptLogin() }
withLogin.setOnCheckedChangeListener { _, b -> binding.withLogin.setOnCheckedChangeListener { _, b ->
isWithLogin = !isWithLogin isWithLogin = !isWithLogin
val visi: Int = if (b) View.VISIBLE else View.GONE val visi: Int = if (b) View.VISIBLE else View.GONE
loginLayout.visibility = visi binding.loginLayout.visibility = visi
passwordLayout.visibility = visi binding.passwordLayout.visibility = visi
} }
withHttpLogin.setOnCheckedChangeListener { _, b -> binding.withHttpLogin.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin isWithHTTPLogin = !isWithHTTPLogin
val visi: Int = if (b) View.VISIBLE else View.GONE val visi: Int = if (b) View.VISIBLE else View.GONE
httpLoginInput.visibility = visi binding.httpLoginInput.visibility = visi
httpPasswordInput.visibility = visi binding.httpPasswordInput.visibility = visi
} }
} }
@ -124,25 +127,25 @@ class LoginActivity : AppCompatActivity() {
private fun attemptLogin() { private fun attemptLogin() {
// Reset errors. // Reset errors.
urlView.error = null binding.urlView.error = null
loginView.error = null binding.loginView.error = null
httpLoginView.error = null binding.httpLoginView.error = null
passwordView.error = null binding.passwordView.error = null
httpPasswordView.error = null binding.httpPasswordView.error = null
// Store values at the time of the login attempt. // Store values at the time of the login attempt.
val url = urlView.text.toString() val url = binding.urlView.text.toString()
val login = loginView.text.toString() val login = binding.loginView.text.toString()
val httpLogin = httpLoginView.text.toString() val httpLogin = binding.httpLoginView.text.toString()
val password = passwordView.text.toString() val password = binding.passwordView.text.toString()
val httpPassword = httpPasswordView.text.toString() val httpPassword = binding.httpPasswordView.text.toString()
var cancel = false var cancel = false
var focusView: View? = null var focusView: View? = null
if (!url.isBaseUrlValid(this@LoginActivity)) { if (!url.isBaseUrlValid(this@LoginActivity)) {
urlView.error = getString(R.string.login_url_problem) binding.urlView.error = getString(R.string.login_url_problem)
focusView = urlView focusView = binding.urlView
cancel = true cancel = true
inValidCount++ inValidCount++
if (inValidCount == 3) { if (inValidCount == 3) {
@ -161,28 +164,28 @@ class LoginActivity : AppCompatActivity() {
if (isWithLogin) { if (isWithLogin) {
if (TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(password)) {
passwordView.error = getString(R.string.error_invalid_password) binding.passwordView.error = getString(R.string.error_invalid_password)
focusView = passwordView focusView = binding.passwordView
cancel = true cancel = true
} }
if (TextUtils.isEmpty(login)) { if (TextUtils.isEmpty(login)) {
loginView.error = getString(R.string.error_field_required) binding.loginView.error = getString(R.string.error_field_required)
focusView = loginView focusView = binding.loginView
cancel = true cancel = true
} }
} }
if (isWithHTTPLogin) { if (isWithHTTPLogin) {
if (TextUtils.isEmpty(httpPassword)) { if (TextUtils.isEmpty(httpPassword)) {
httpPasswordView.error = getString(R.string.error_invalid_password) binding.httpPasswordView.error = getString(R.string.error_invalid_password)
focusView = httpPasswordView focusView = binding.httpPasswordView
cancel = true cancel = true
} }
if (TextUtils.isEmpty(httpLogin)) { if (TextUtils.isEmpty(httpLogin)) {
httpLoginView.error = getString(R.string.error_field_required) binding.httpLoginView.error = getString(R.string.error_field_required)
focusView = httpLoginView focusView = binding.httpLoginView
cancel = true cancel = true
} }
} }
@ -216,11 +219,11 @@ class LoginActivity : AppCompatActivity() {
editor.remove("password") editor.remove("password")
editor.remove("httpPassword") editor.remove("httpPassword")
editor.apply() editor.apply()
urlView.error = getString(R.string.wrong_infos) binding.urlView.error = getString(R.string.wrong_infos)
loginView.error = getString(R.string.wrong_infos) binding.loginView.error = getString(R.string.wrong_infos)
passwordView.error = getString(R.string.wrong_infos) binding.passwordView.error = getString(R.string.wrong_infos)
httpLoginView.error = getString(R.string.wrong_infos) binding.httpLoginView.error = getString(R.string.wrong_infos)
httpPasswordView.error = getString(R.string.wrong_infos) binding.httpPasswordView.error = getString(R.string.wrong_infos)
showProgress(false) showProgress(false)
} }
@ -248,28 +251,28 @@ class LoginActivity : AppCompatActivity() {
private fun showProgress(show: Boolean) { private fun showProgress(show: Boolean) {
val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime) val shortAnimTime = resources.getInteger(android.R.integer.config_shortAnimTime)
loginForm.visibility = if (show) View.GONE else View.VISIBLE binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
loginForm binding.loginForm
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 0F else 1F if (show) 0F else 1F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
loginForm.visibility = if (show) View.GONE else View.VISIBLE binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
} }
} }
) )
loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
loginProgress binding.loginProgress
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 1F else 0F if (show) 1F else 0F
).setListener(object : AnimatorListenerAdapter() { ).setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
} }
} }
) )

View File

@ -4,12 +4,18 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import apps.amine.bou.readerforselfoss.databinding.ActivityAddSourceBinding
import apps.amine.bou.readerforselfoss.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val intent = Intent(this, LoginActivity::class.java) val intent = Intent(this, LoginActivity::class.java)

View File

@ -3,12 +3,15 @@ package apps.amine.bou.readerforselfoss
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.widget.ImageView import android.widget.ImageView
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import apps.amine.bou.readerforselfoss.api.selfoss.ApiVersion
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
@ -16,10 +19,20 @@ import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.UUID.randomUUID import java.util.UUID.randomUUID
var dateTimeFormatter = "yyyy-MM-dd HH:mm:ss"
class MyApp : MultiDexApplication() { class MyApp : MultiDexApplication() {
private lateinit var config: Config private lateinit var config: Config
private lateinit var api: SelfossApi
private lateinit var settings: SharedPreferences
private lateinit var sharedPref: SharedPreferences
private var apiVersionMajor: Int = 0
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -39,6 +52,19 @@ class MyApp : MultiDexApplication() {
tryToHandleBug() tryToHandleBug()
handleNotificationChannels() handleNotificationChannels()
sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
apiVersionMajor = sharedPref.getInt("apiVersionMajor", 0)
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
api = SelfossApi(
this,
null,
settings.getBoolean("isSelfSignedCert", false),
sharedPref.getString("api_timeout", "-1")!!.toLong()
)
getApiMajorVersion()
} }
private fun handleNotificationChannels() { private fun handleNotificationChannels() {
@ -103,4 +129,24 @@ class MyApp : MultiDexApplication() {
} }
} }
} }
private fun getApiMajorVersion() {
api.apiVersion.enqueue(object : Callback<ApiVersion> {
override fun onFailure(call: Call<ApiVersion>, t: Throwable) {
if (apiVersionMajor >= 4) {
dateTimeFormatter = "yyyy-MM-dd'T'HH:mm:ssXXX"
}
}
override fun onResponse(call: Call<ApiVersion>, response: Response<ApiVersion>) {
val version = response.body() as ApiVersion
apiVersionMajor = version.getApiMajorVersion()
sharedPref.edit().putInt("apiVersionMajor", apiVersionMajor).commit()
if (apiVersionMajor >= 4) {
dateTimeFormatter = "yyyy-MM-dd'T'HH:mm:ssXXX"
}
}
})
}
} }

View File

@ -20,6 +20,8 @@ import androidx.room.Room
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding
import apps.amine.bou.readerforselfoss.databinding.ActivityReaderBinding
import apps.amine.bou.readerforselfoss.fragments.ArticleFragment import apps.amine.bou.readerforselfoss.fragments.ArticleFragment
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
@ -35,7 +37,6 @@ import apps.amine.bou.readerforselfoss.utils.persistence.toEntity
import apps.amine.bou.readerforselfoss.utils.succeeded import apps.amine.bou.readerforselfoss.utils.succeeded
import apps.amine.bou.readerforselfoss.utils.toggleStar import apps.amine.bou.readerforselfoss.utils.toggleStar
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_reader.*
import me.relex.circleindicator.CircleIndicator import me.relex.circleindicator.CircleIndicator
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
@ -54,6 +55,7 @@ class ReaderActivity : AppCompatActivity() {
private lateinit var db: AppDatabase private lateinit var db: AppDatabase
private lateinit var prefs: SharedPreferences private lateinit var prefs: SharedPreferences
private lateinit var binding: ActivityReaderBinding
private var activeAlignment: Int = 1 private var activeAlignment: Int = 1
val JUSTIFY = 1 val JUSTIFY = 1
@ -76,8 +78,10 @@ class ReaderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityReaderBinding.inflate(layoutInflater)
val view = binding.root
setContentView(R.layout.activity_reader) setContentView(view)
db = Room.databaseBuilder( db = Room.databaseBuilder(
applicationContext, applicationContext,
@ -85,12 +89,12 @@ class ReaderActivity : AppCompatActivity() {
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
val scoop = Scoop.getInstance() val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, toolBar) scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
} }
setSupportActionBar(toolBar) setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
@ -119,9 +123,9 @@ class ReaderActivity : AppCompatActivity() {
readItem(allItems[currentItem]) readItem(allItems[currentItem])
pager.adapter = binding.pager.adapter =
ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity)) ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
pager.currentItem = currentItem binding.pager.currentItem = currentItem
} }
override fun onResume() { override fun onResume() {
@ -129,10 +133,10 @@ class ReaderActivity : AppCompatActivity() {
notifyAdapter() notifyAdapter()
pager.setPageTransformer(true, DepthPageTransformer()) binding.pager.setPageTransformer(true, DepthPageTransformer())
(indicator as CircleIndicator).setViewPager(pager) (binding.indicator as CircleIndicator).setViewPager(binding.pager)
pager.addOnPageChangeListener( binding.pager.addOnPageChangeListener(
object : ViewPager.SimpleOnPageChangeListener() { object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
@ -142,7 +146,7 @@ class ReaderActivity : AppCompatActivity() {
} else { } else {
canFavorite() canFavorite()
} }
readItem(allItems[pager.currentItem]) readItem(allItems[binding.pager.currentItem])
} }
} }
) )
@ -181,19 +185,19 @@ class ReaderActivity : AppCompatActivity() {
} }
private fun notifyAdapter() { private fun notifyAdapter() {
(pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged() (binding.pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
if (markOnScroll) { if (markOnScroll) {
pager.clearOnPageChangeListeners() binding.pager.clearOnPageChangeListeners()
} }
} }
override fun onSaveInstanceState(oldInstanceState: Bundle) { override fun onSaveInstanceState(oldInstanceState: Bundle) {
super.onSaveInstanceState(oldInstanceState) super.onSaveInstanceState(oldInstanceState)
oldInstanceState!!.clear() oldInstanceState.clear()
} }
private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) : private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
@ -245,14 +249,14 @@ class ReaderActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
fun afterSave() { fun afterSave() {
allItems[pager.currentItem] = allItems[binding.pager.currentItem] =
allItems[pager.currentItem].toggleStar() allItems[binding.pager.currentItem].toggleStar()
notifyAdapter() notifyAdapter()
canRemoveFromFavorite() canRemoveFromFavorite()
} }
fun afterUnsave() { fun afterUnsave() {
allItems[pager.currentItem] = allItems[pager.currentItem].toggleStar() allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem].toggleStar()
notifyAdapter() notifyAdapter()
canFavorite() canFavorite()
} }
@ -264,7 +268,7 @@ class ReaderActivity : AppCompatActivity() {
} }
R.id.save -> { R.id.save -> {
if (this@ReaderActivity.isNetworkAccessible(null)) { if (this@ReaderActivity.isNetworkAccessible(null)) {
api.starrItem(allItems[pager.currentItem].id) api.starrItem(allItems[binding.pager.currentItem].id)
.enqueue(object : Callback<SuccessResponse> { .enqueue(object : Callback<SuccessResponse> {
override fun onResponse( override fun onResponse(
call: Call<SuccessResponse>, call: Call<SuccessResponse>,
@ -286,14 +290,14 @@ class ReaderActivity : AppCompatActivity() {
}) })
} else { } else {
thread { thread {
db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, true, false)) db.actionsDao().insertAllActions(ActionEntity(allItems[binding.pager.currentItem].id, false, false, true, false))
afterSave() afterSave()
} }
} }
} }
R.id.unsave -> { R.id.unsave -> {
if (this@ReaderActivity.isNetworkAccessible(null)) { if (this@ReaderActivity.isNetworkAccessible(null)) {
api.unstarrItem(allItems[pager.currentItem].id) api.unstarrItem(allItems[binding.pager.currentItem].id)
.enqueue(object : Callback<SuccessResponse> { .enqueue(object : Callback<SuccessResponse> {
override fun onResponse( override fun onResponse(
call: Call<SuccessResponse>, call: Call<SuccessResponse>,
@ -315,7 +319,7 @@ class ReaderActivity : AppCompatActivity() {
}) })
} else { } else {
thread { thread {
db.actionsDao().insertAllActions(ActionEntity(allItems[pager.currentItem].id, false, false, false, true)) db.actionsDao().insertAllActions(ActionEntity(allItems[binding.pager.currentItem].id, false, false, false, true))
afterUnsave() afterUnsave()
} }
} }

View File

@ -12,12 +12,13 @@ import android.widget.Toast
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Source import apps.amine.bou.readerforselfoss.api.selfoss.Source
import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding
import apps.amine.bou.readerforselfoss.databinding.ActivitySourcesBinding
import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.themes.Toppings import apps.amine.bou.readerforselfoss.themes.Toppings
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import kotlinx.android.synthetic.main.activity_sources.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -25,31 +26,34 @@ import retrofit2.Response
class SourcesActivity : AppCompatActivity() { class SourcesActivity : AppCompatActivity() {
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
private lateinit var binding: ActivitySourcesBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@SourcesActivity) appColors = AppColors(this@SourcesActivity)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivitySourcesBinding.inflate(layoutInflater)
val view = binding.root
setContentView(R.layout.activity_sources) setContentView(view)
val scoop = Scoop.getInstance() val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, toolbar) scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value) scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
} }
setSupportActionBar(toolbar) setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
fab.rippleColor = appColors.colorAccentDark binding.fab.rippleColor = appColors.colorAccentDark
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent) binding.fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
} }
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
recyclerView.clearOnScrollListeners() binding.recyclerView.clearOnScrollListeners()
} }
override fun onResume() { override fun onResume() {
@ -68,8 +72,8 @@ class SourcesActivity : AppCompatActivity() {
) )
var items: ArrayList<Source> = ArrayList() var items: ArrayList<Source> = ArrayList()
recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
if (this@SourcesActivity.isNetworkAccessible(this@SourcesActivity.findViewById(R.id.recyclerView))) { if (this@SourcesActivity.isNetworkAccessible(this@SourcesActivity.findViewById(R.id.recyclerView))) {
api.sources.enqueue(object : Callback<List<Source>> { api.sources.enqueue(object : Callback<List<Source>> {
@ -81,7 +85,7 @@ class SourcesActivity : AppCompatActivity() {
items = response.body() as ArrayList<Source> items = response.body() as ArrayList<Source>
} }
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api) val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api)
recyclerView.adapter = mAdapter binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
if (items.isEmpty()) { if (items.isEmpty()) {
Toast.makeText( Toast.makeText(
@ -102,7 +106,7 @@ class SourcesActivity : AppCompatActivity() {
}) })
} }
fab.setOnClickListener { binding.fab.setOnClickListener {
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java)) startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
} }
} }

View File

@ -14,6 +14,7 @@ import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.databinding.CardItemBinding
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.AppColors
@ -34,11 +35,6 @@ import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.like.LikeButton import com.like.LikeButton
import com.like.OnLikeListener import com.like.OnLikeListener
import kotlinx.android.synthetic.main.card_item.view.*
import kotlinx.android.synthetic.main.card_item.view.itemImage
import kotlinx.android.synthetic.main.card_item.view.sourceTitleAndDate
import kotlinx.android.synthetic.main.card_item.view.title
import kotlinx.android.synthetic.main.list_item.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -64,77 +60,79 @@ class ItemCardAdapter(
c.resources.getDimension(R.dimen.card_image_max_height).toInt() c.resources.getDimension(R.dimen.card_image_max_height).toInt()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate(R.layout.card_item, parent, false) as CardView val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(v) return ViewHolder(binding)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position] with(holder) {
val itm = items[position]
holder.mView.favButton.isLiked = itm.starred binding.favButton.isLiked = itm.starred
holder.mView.title.text = itm.getTitleDecoded() binding.title.text = itm.getTitleDecoded()
holder.mView.title.setTextColor(ContextCompat.getColor( binding.title.setTextColor(ContextCompat.getColor(
c, c,
appColors.textColor appColors.textColor
)) ))
holder.mView.title.setOnTouchListener(LinkOnTouchListener()) binding.title.setOnTouchListener(LinkOnTouchListener())
holder.mView.title.setLinkTextColor(appColors.colorAccent) binding.title.setLinkTextColor(appColors.colorAccent)
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText() binding.sourceTitleAndDate.text = itm.sourceAndDateText()
holder.mView.sourceTitleAndDate.setTextColor(ContextCompat.getColor( binding.sourceTitleAndDate.setTextColor(ContextCompat.getColor(
c, c,
appColors.textColor appColors.textColor
)) ))
if (!fullHeightCards) { if (!fullHeightCards) {
holder.mView.itemImage.maxHeight = imageMaxHeight binding.itemImage.maxHeight = imageMaxHeight
holder.mView.itemImage.scaleType = ScaleType.CENTER_CROP binding.itemImage.scaleType = ScaleType.CENTER_CROP
}
if (itm.getThumbnail(c).isEmpty()) {
binding.itemImage.visibility = View.GONE
Glide.with(c).clear(binding.itemImage)
binding.itemImage.setImageDrawable(null)
} else {
binding.itemImage.visibility = View.VISIBLE
c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage)
}
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.getSourceTitle().toTextDrawableString(c), color)
binding.sourceImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(c), binding.sourceImage)
}
binding.favButton.isLiked = itm.starred
} }
if (itm.getThumbnail(c).isEmpty()) {
holder.mView.itemImage.visibility = View.GONE
Glide.with(c).clear(holder.mView.itemImage)
holder.mView.itemImage.setImageDrawable(null)
} else {
holder.mView.itemImage.visibility = View.VISIBLE
c.bitmapCenterCrop(config, itm.getThumbnail(c), holder.mView.itemImage)
}
if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.getSourceTitle().toTextDrawableString(c), color)
holder.mView.sourceImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.sourceImage)
}
holder.mView.favButton.isLiked = itm.starred
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return items.size return items.size
} }
inner class ViewHolder(val mView: CardView) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) {
init { init {
mView.setCardBackgroundColor(appColors.cardBackgroundColor) binding.root.setCardBackgroundColor(appColors.cardBackgroundColor)
handleClickListeners() handleClickListeners()
handleCustomTabActions() handleCustomTabActions()
} }
private fun handleClickListeners() { private fun handleClickListeners() {
mView.favButton.setOnLikeListener(object : OnLikeListener { binding.favButton.setOnLikeListener(object : OnLikeListener {
override fun liked(likeButton: LikeButton) { override fun liked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[bindingAdapterPosition]
if (c.isNetworkAccessible(null)) { if (c.isNetworkAccessible(null)) {
api.starrItem(id).enqueue(object : Callback<SuccessResponse> { api.starrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse( override fun onResponse(
@ -147,7 +145,7 @@ class ItemCardAdapter(
call: Call<SuccessResponse>, call: Call<SuccessResponse>,
t: Throwable t: Throwable
) { ) {
mView.favButton.isLiked = false binding.favButton.isLiked = false
Toast.makeText( Toast.makeText(
c, c,
R.string.cant_mark_favortie, R.string.cant_mark_favortie,
@ -163,7 +161,7 @@ class ItemCardAdapter(
} }
override fun unLiked(likeButton: LikeButton) { override fun unLiked(likeButton: LikeButton) {
val (id) = items[adapterPosition] val (id) = items[bindingAdapterPosition]
if (c.isNetworkAccessible(null)) { if (c.isNetworkAccessible(null)) {
api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> { api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> {
override fun onResponse( override fun onResponse(
@ -176,7 +174,7 @@ class ItemCardAdapter(
call: Call<SuccessResponse>, call: Call<SuccessResponse>,
t: Throwable t: Throwable
) { ) {
mView.favButton.isLiked = true binding.favButton.isLiked = true
Toast.makeText( Toast.makeText(
c, c,
R.string.cant_unmark_favortie, R.string.cant_unmark_favortie,
@ -192,13 +190,13 @@ class ItemCardAdapter(
} }
}) })
mView.shareBtn.setOnClickListener { binding.shareBtn.setOnClickListener {
val item = items[adapterPosition] val item = items[bindingAdapterPosition]
c.shareLink(item.getLinkDecoded(), item.getTitleDecoded()) c.shareLink(item.getLinkDecoded(), item.getTitleDecoded())
} }
mView.browserBtn.setOnClickListener { binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[adapterPosition]) c.openInBrowserAsNewTask(items[bindingAdapterPosition])
} }
} }
@ -206,11 +204,11 @@ class ItemCardAdapter(
val customTabsIntent = c.buildCustomTabsIntent() val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app) helper.bindCustomTabsService(app)
mView.setOnClickListener { binding.root.setOnClickListener {
c.openItemUrl( c.openItemUrl(
items, items,
adapterPosition, bindingAdapterPosition,
items[adapterPosition].getLinkDecoded(), items[bindingAdapterPosition].getLinkDecoded(),
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,

View File

@ -18,6 +18,7 @@ import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.databinding.ListItemBinding
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
@ -35,7 +36,6 @@ import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import com.like.LikeButton import com.like.LikeButton
import com.like.OnLikeListener import com.like.OnLikeListener
import kotlinx.android.synthetic.main.list_item.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -59,59 +59,57 @@ class ItemListAdapter(
private val c: Context = app.baseContext private val c: Context = app.baseContext
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate( val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
R.layout.list_item, return ViewHolder(binding)
parent,
false
) as ConstraintLayout
return ViewHolder(v)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position] with(holder) {
val itm = items[position]
holder.mView.title.text = itm.getTitleDecoded() binding.title.text = itm.getTitleDecoded()
holder.mView.title.setTextColor(ContextCompat.getColor( binding.title.setTextColor(ContextCompat.getColor(
c, c,
appColors.textColor appColors.textColor
)) ))
holder.mView.title.setOnTouchListener(LinkOnTouchListener()) binding.title.setOnTouchListener(LinkOnTouchListener())
holder.mView.title.setLinkTextColor(appColors.colorAccent) binding.title.setLinkTextColor(appColors.colorAccent)
holder.mView.sourceTitleAndDate.text = itm.sourceAndDateText() binding.sourceTitleAndDate.text = itm.sourceAndDateText()
holder.mView.sourceTitleAndDate.setTextColor(ContextCompat.getColor( binding.sourceTitleAndDate.setTextColor(ContextCompat.getColor(
c, c,
appColors.textColor appColors.textColor
)) ))
if (itm.getThumbnail(c).isEmpty()) { if (itm.getThumbnail(c).isEmpty()) {
if (itm.getIcon(c).isEmpty()) { if (itm.getIcon(c).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle()) val color = generator.getColor(itm.getSourceTitle())
val drawable = val drawable =
TextDrawable TextDrawable
.builder() .builder()
.round() .round()
.build(itm.getSourceTitle().toTextDrawableString(c), color) .build(itm.getSourceTitle().toTextDrawableString(c), color)
holder.mView.itemImage.setImageDrawable(drawable) binding.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage)
}
} else { } else {
c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.itemImage) c.bitmapCenterCrop(config, itm.getThumbnail(c), binding.itemImage)
} }
} else {
c.bitmapCenterCrop(config, itm.getThumbnail(c), holder.mView.itemImage)
} }
} }
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
init { init {
handleCustomTabActions() handleCustomTabActions()
@ -121,11 +119,11 @@ class ItemListAdapter(
val customTabsIntent = c.buildCustomTabsIntent() val customTabsIntent = c.buildCustomTabsIntent()
helper.bindCustomTabsService(app) helper.bindCustomTabsService(app)
mView.setOnClickListener { binding.root.setOnClickListener {
c.openItemUrl( c.openItemUrl(
items, items,
adapterPosition, bindingAdapterPosition,
items[adapterPosition].getLinkDecoded(), items[bindingAdapterPosition].getLinkDecoded(),
customTabsIntent, customTabsIntent,
internalBrowser, internalBrowser,
articleViewer, articleViewer,

View File

@ -12,13 +12,13 @@ import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Source import apps.amine.bou.readerforselfoss.api.selfoss.Source
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.databinding.SourceListItemBinding
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString import apps.amine.bou.readerforselfoss.utils.toTextDrawableString
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.android.synthetic.main.source_list_item.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -31,14 +31,11 @@ class SourcesListAdapter(
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL
private lateinit var config: Config private lateinit var config: Config
private lateinit var binding: SourceListItemBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(c).inflate( binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
R.layout.source_list_item, return ViewHolder(binding.root)
parent,
false
) as ConstraintLayout
return ViewHolder(v)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
@ -53,12 +50,12 @@ class SourcesListAdapter(
.builder() .builder()
.round() .round()
.build(itm.getTitleDecoded().toTextDrawableString(c), color) .build(itm.getTitleDecoded().toTextDrawableString(c), color)
holder.mView.itemImage.setImageDrawable(drawable) binding.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularBitmapDrawable(config, itm.getIcon(c), holder.mView.itemImage) c.circularBitmapDrawable(config, itm.getIcon(c), binding.itemImage)
} }
holder.mView.sourceTitle.text = itm.getTitleDecoded() binding.sourceTitle.text = itm.getTitleDecoded()
} }
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size

View File

@ -172,6 +172,15 @@ class SelfossApi(
fun allItems(): Call<List<Item>> = fun allItems(): Call<List<Item>> =
service.allItems(userName, password) service.allItems(userName, password)
fun allNewItems(): Call<List<Item>> =
getItems("unread", null, null, null, 200, 0)
fun allReadItems(): Call<List<Item>> =
getItems("read", null, null, null, 200, 0)
fun allStarredItems(): Call<List<Item>> =
getItems("read", null, null, null, 200, 0)
private fun getItems( private fun getItems(
type: String, type: String,
tag: String?, tag: String?,
@ -206,6 +215,9 @@ class SelfossApi(
fun update(): Call<String> = fun update(): Call<String> =
service.update(userName, password) service.update(userName, password)
val apiVersion: Call<ApiVersion>
get() = service.version()
val sources: Call<List<Source>> val sources: Call<List<Source>>
get() = service.sources(userName, password) get() = service.sources(userName, password)

View File

@ -48,6 +48,19 @@ data class Spout(
@SerializedName("description") val description: String @SerializedName("description") val description: String
) )
data class ApiVersion(
@SerializedName("version") val version: String,
@SerializedName("apiversion") val apiversion: String
) {
fun getApiMajorVersion() : Int {
var versionNumber = 0
if (apiversion != null) {
versionNumber = apiversion.substringBefore(".").toInt()
}
return versionNumber
}
}
data class Source( data class Source(
@SerializedName("id") val id: String, @SerializedName("id") val id: String,
@SerializedName("title") val title: String, @SerializedName("title") val title: String,
@ -156,7 +169,7 @@ data class Item(
fun preloadImages(context: Context) : Boolean { fun preloadImages(context: Context) : Boolean {
val imageUrls = this.getImages() val imageUrls = this.getImages()
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
try { try {
@ -164,7 +177,7 @@ data class Item(
if ( URLUtil.isValidUrl(url)) { if ( URLUtil.isValidUrl(url)) {
val image = Glide.with(context).asBitmap() val image = Glide.with(context).asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(url).submit().get() .load(url).submit()
} }
} }
} catch (e : Error) { } catch (e : Error) {

View File

@ -103,6 +103,9 @@ internal interface SelfossService {
@Query("password") password: String @Query("password") password: String
): Call<List<Source>> ): Call<List<Source>>
@GET("api/about")
fun version(): Call<ApiVersion>
@DELETE("source/{id}") @DELETE("source/{id}")
fun deleteSource( fun deleteSource(
@Path("id") id: String, @Path("id") id: String,

View File

@ -66,7 +66,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
sharedPref.getString("api_timeout", "-1")!!.toLong() sharedPref.getString("api_timeout", "-1")!!.toLong()
) )
api.allItems().enqueue(object : Callback<List<Item>> { api.allNewItems().enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) { override fun onFailure(call: Call<List<Item>>, t: Throwable) {
Timer("", false).schedule(4000) { Timer("", false).schedule(4000) {
notificationManager.cancel(1) notificationManager.cancel(1)
@ -77,42 +77,38 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
call: Call<List<Item>>, call: Call<List<Item>>,
response: Response<List<Item>> response: Response<List<Item>>
) { ) {
thread { storeItems(response, true, notifyNewItems, notificationManager)
if (response.body() != null) {
val apiItems = (response.body() as ArrayList<Item>)
db.itemsDao().deleteAllItems()
db.itemsDao()
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
val newSize = apiItems.filter { it.unread }.size
if (notifyNewItems && newSize > 0) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val newItemsNotification = NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
.setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText(context.getString(R.string.new_items_notification_text, newSize))
.setPriority(PRIORITY_DEFAULT)
.setChannelId(Config.newItemsChannelId)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
Timer("", false).schedule(4000) {
notificationManager.notify(2, newItemsNotification.build())
}
}
apiItems.map {it.preloadImages(context)}
}
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}
}
} }
}) })
api.allReadItems().enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}
}
override fun onResponse(
call: Call<List<Item>>,
response: Response<List<Item>>
) {
storeItems(response, false, notifyNewItems, notificationManager)
}
})
api.allStarredItems().enqueue(object : Callback<List<Item>> {
override fun onFailure(call: Call<List<Item>>, t: Throwable) {
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}
}
override fun onResponse(
call: Call<List<Item>>,
response: Response<List<Item>>
) {
storeItems(response, false, notifyNewItems, notificationManager)
}
})
thread { thread {
val actions = db.actionsDao().actions() val actions = db.actionsDao().actions()
@ -132,6 +128,46 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
return Result.success() return Result.success()
} }
private fun storeItems(response: Response<List<Item>>, newItems: Boolean, notifyNewItems: Boolean, notificationManager: NotificationManager) {
thread {
if (response.body() != null) {
val apiItems = (response.body() as ArrayList<Item>)
if (newItems) {
db.itemsDao().deleteAllItems()
}
db.itemsDao()
.insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray())
val newSize = apiItems.filter { it.unread }.size
if (newItems && notifyNewItems && newSize > 0) {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val newItemsNotification = NotificationCompat.Builder(applicationContext, Config.newItemsChannelId)
.setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText(context.getString(R.string.new_items_notification_text, newSize))
.setPriority(PRIORITY_DEFAULT)
.setChannelId(Config.newItemsChannelId)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
Timer("", false).schedule(4000) {
notificationManager.notify(2, newItemsNotification.build())
}
}
apiItems.map { it.preloadImages(context) }
}
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}
}
}
private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) { private fun <T> doAndReportOnFail(call: Call<T>, action: ActionEntity) {
call.enqueue(object : Callback<T> { call.enqueue(object : Callback<T> {
override fun onResponse( override fun onResponse(

View File

@ -29,6 +29,7 @@ import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.databinding.FragmentArticleBinding
import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase
import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity
import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2
@ -50,7 +51,6 @@ 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 com.github.rubensousa.floatingtoolbar.FloatingToolbar import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import kotlinx.android.synthetic.main.fragment_article.view.*
import retrofit2.Call import retrofit2.Call
import retrofit2.Callback import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
@ -77,8 +77,8 @@ class ArticleFragment : Fragment() {
private lateinit var db: AppDatabase private lateinit var db: AppDatabase
private lateinit var textAlignment: String private lateinit var textAlignment: String
private lateinit var config: Config private lateinit var config: Config
private var _binding: FragmentArticleBinding? = null
private var rootView: ViewGroup? = null private val binding get() = _binding!!
private lateinit var prefs: SharedPreferences private lateinit var prefs: SharedPreferences
@ -94,16 +94,16 @@ class ArticleFragment : Fragment() {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(activity!!) appColors = AppColors(requireActivity())
config = Config(activity!!) config = Config(requireActivity())
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
pageNumber = arguments!!.getInt(ARG_POSITION) pageNumber = requireArguments().getInt(ARG_POSITION)
allItems = arguments!!.getParcelableArrayList<Item>(ARG_ITEMS) as ArrayList<Item> allItems = requireArguments().getParcelableArrayList<Item>(ARG_ITEMS) as ArrayList<Item>
db = Room.databaseBuilder( db = Room.databaseBuilder(
context!!, requireContext(),
AppDatabase::class.java, "selfoss-database" AppDatabase::class.java, "selfoss-database"
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
} }
@ -114,13 +114,12 @@ class ArticleFragment : Fragment() {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
try { try {
rootView = inflater _binding = FragmentArticleBinding.inflate(inflater, container, false)
.inflate(R.layout.fragment_article, container, false) as ViewGroup
url = allItems[pageNumber.toInt()].getLinkDecoded() url = allItems[pageNumber.toInt()].getLinkDecoded()
contentText = allItems[pageNumber.toInt()].content contentText = allItems[pageNumber.toInt()].content
contentTitle = allItems[pageNumber.toInt()].getTitleDecoded() contentTitle = allItems[pageNumber.toInt()].getTitleDecoded()
contentImage = allItems[pageNumber.toInt()].getThumbnail(activity!!) contentImage = allItems[pageNumber.toInt()].getThumbnail(requireActivity())
contentSource = allItems[pageNumber.toInt()].sourceAndDateText() contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
allImages = allItems[pageNumber.toInt()].getImages() allImages = allItems[pageNumber.toInt()].getImages()
@ -130,11 +129,11 @@ class ArticleFragment : Fragment() {
font = prefs.getString("reader_font", "")!! font = prefs.getString("reader_font", "")!!
if (font.isNotEmpty()) { if (font.isNotEmpty()) {
resId = context!!.resources.getIdentifier(font, "font", context!!.packageName) resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
typeface = try { typeface = try {
ResourcesCompat.getFont(context!!, resId)!! ResourcesCompat.getFont(requireContext(), resId)!!
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
// ACRA.getErrorReporter().maybeHandleSilentException(Throwable("Font loading issue: ${e.message}"), context!!) // ACRA.getErrorReporter().maybeHandleSilentException(Throwable("Font loading issue: ${e.message}"), requireContext())
// Just to be sure // Just to be sure
null null
} }
@ -142,27 +141,27 @@ class ArticleFragment : Fragment() {
refreshAlignment() refreshAlignment()
val settings = activity!!.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) val settings = requireActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val api = SelfossApi( val api = SelfossApi(
context!!, requireContext(),
activity!!, requireActivity(),
settings.getBoolean("isSelfSignedCert", false), settings.getBoolean("isSelfSignedCert", false),
prefs.getString("api_timeout", "-1")!!.toLong() prefs.getString("api_timeout", "-1")!!.toLong()
) )
fab = rootView!!.fab fab = binding.fab
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent) fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
fab.rippleColor = appColors.colorAccentDark fab.rippleColor = appColors.colorAccentDark
val floatingToolbar: FloatingToolbar = rootView!!.floatingToolbar val floatingToolbar: FloatingToolbar = binding.floatingToolbar
floatingToolbar.attachFab(fab) floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(appColors.colorAccent) floatingToolbar.background = ColorDrawable(appColors.colorAccent)
val customTabsIntent = activity!!.buildCustomTabsIntent() val customTabsIntent = requireActivity().buildCustomTabsIntent()
mCustomTabActivityHelper = CustomTabActivityHelper() mCustomTabActivityHelper = CustomTabActivityHelper()
mCustomTabActivityHelper!!.bindCustomTabsService(activity) mCustomTabActivityHelper!!.bindCustomTabsService(activity)
@ -172,17 +171,17 @@ class ArticleFragment : Fragment() {
override fun onItemClick(item: MenuItem) { override fun onItemClick(item: MenuItem) {
when (item.itemId) { when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent, prefs) R.id.more_action -> getContentFromMercury(customTabsIntent, prefs)
R.id.share_action -> activity!!.shareLink(url, contentTitle) R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> activity!!.openItemUrl( R.id.open_action -> requireActivity().openItemUrl(
allItems, allItems,
pageNumber.toInt(), pageNumber.toInt(),
url, url,
customTabsIntent, customTabsIntent,
false, false,
false, false,
activity!! requireActivity()
) )
R.id.unread_action -> if ((context != null && context!!.isNetworkAccessible(null)) || context == null) { R.id.unread_action -> if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) {
api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue( api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue(
object : Callback<SuccessResponse> { object : Callback<SuccessResponse> {
override fun onResponse( override fun onResponse(
@ -212,35 +211,35 @@ class ArticleFragment : Fragment() {
} }
) )
rootView!!.source.text = contentSource binding.source.text = contentSource
if (typeface != null) { if (typeface != null) {
rootView!!.source.typeface = typeface binding.source.typeface = typeface
} }
if (contentText.isEmptyOrNullOrNullString()) { if (contentText.isEmptyOrNullOrNullString()) {
getContentFromMercury(customTabsIntent, prefs) getContentFromMercury(customTabsIntent, prefs)
} else { } else {
rootView!!.titleView.text = contentTitle binding.titleView.text = contentTitle
if (typeface != null) { if (typeface != null) {
rootView!!.titleView.typeface = typeface binding.titleView.typeface = typeface
} }
htmlToWebview() htmlToWebview()
if (!contentImage.isEmptyOrNullOrNullString() && context != null) { if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
rootView!!.imageView.visibility = View.VISIBLE binding.imageView.visibility = View.VISIBLE
Glide Glide
.with(context!!) .with(requireContext())
.asBitmap() .asBitmap()
.loadMaybeBasicAuth(config, contentImage) .loadMaybeBasicAuth(config, contentImage)
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(rootView!!.imageView) .into(binding.imageView)
} else { } else {
rootView!!.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
} }
rootView!!.nestedScrollView.setOnScrollChangeListener( binding.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY -> NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) { if (scrollY > oldScrollY) {
fab.hide() fab.hide()
@ -251,22 +250,27 @@ class ArticleFragment : Fragment() {
) )
} catch (e: InflateException) { } catch (e: InflateException) {
AlertDialog.Builder(context!!) AlertDialog.Builder(requireContext())
.setMessage(context!!.getString(R.string.webview_dialog_issue_message)) .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(context!!.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
) { dialog, which -> ) { dialog, which ->
val sharedPref = PreferenceManager.getDefaultSharedPreferences(context!!) val sharedPref = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = sharedPref.edit() val editor = sharedPref.edit()
editor.putBoolean("prefer_article_viewer", false) editor.putBoolean("prefer_article_viewer", false)
editor.commit() editor.commit()
activity!!.finish() requireActivity().finish()
} }
.create() .create()
.show() .show()
} }
return rootView return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
} }
private fun refreshAlignment() { private fun refreshAlignment() {
@ -281,8 +285,8 @@ class ArticleFragment : Fragment() {
customTabsIntent: CustomTabsIntent, customTabsIntent: CustomTabsIntent,
prefs: SharedPreferences prefs: SharedPreferences
) { ) {
if ((context != null && context!!.isNetworkAccessible(null)) || context == null) { if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) {
rootView!!.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi() val parser = MercuryApi()
parser.parseUrl(url).enqueue( parser.parseUrl(url).enqueue(
@ -295,9 +299,9 @@ class ArticleFragment : Fragment() {
try { try {
if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) {
try { try {
rootView!!.titleView.text = response.body()!!.title binding.titleView.text = response.body()!!.title
if (typeface != null) { if (typeface != null) {
rootView!!.titleView.typeface = typeface binding.titleView.typeface = typeface
} }
try { try {
// Note: Mercury may return relative urls... If it does the url val will not be changed. // Note: Mercury may return relative urls... If it does the url val will not be changed.
@ -317,18 +321,18 @@ class ArticleFragment : Fragment() {
try { try {
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) {
rootView!!.imageView.visibility = View.VISIBLE binding.imageView.visibility = View.VISIBLE
try { try {
Glide Glide
.with(context!!) .with(requireContext())
.asBitmap() .asBitmap()
.loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty()) .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(rootView!!.imageView) .into(binding.imageView)
} catch (e: Exception) { } catch (e: Exception) {
} }
} else { } else {
rootView!!.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { if (context != null) {
@ -336,9 +340,9 @@ class ArticleFragment : Fragment() {
} }
try { try {
rootView!!.nestedScrollView.scrollTo(0, 0) binding.nestedScrollView.scrollTo(0, 0)
rootView!!.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { if (context != null) {
} }
@ -370,32 +374,32 @@ class ArticleFragment : Fragment() {
val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent) val stringColor = String.format("#%06X", 0xFFFFFF and appColors.colorAccent)
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = context!!.obtainStyledAttributes(resId, attrs) val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
rootView!!.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.settings.standardFontFamily = a.getString(0)
rootView!!.webcontent.visibility = View.VISIBLE binding.webcontent.visibility = View.VISIBLE
val (textColor, backgroundColor) = if (appColors.isDarkTheme) { val (textColor, backgroundColor) = if (appColors.isDarkTheme) {
if (context != null) { if (context != null) {
rootView!!.webcontent.setBackgroundColor( binding.webcontent.setBackgroundColor(
ContextCompat.getColor( ContextCompat.getColor(
context!!, requireContext(),
R.color.dark_webview R.color.dark_webview
) )
) )
Pair(ContextCompat.getColor(context!!, R.color.dark_webview_text), ContextCompat.getColor(context!!, R.color.dark_webview)) Pair(ContextCompat.getColor(requireContext(), R.color.dark_webview_text), ContextCompat.getColor(requireContext(), R.color.dark_webview))
} else { } else {
Pair(null, null) Pair(null, null)
} }
} else { } else {
if (context != null) { if (context != null) {
rootView!!.webcontent.setBackgroundColor( binding.webcontent.setBackgroundColor(
ContextCompat.getColor( ContextCompat.getColor(
context!!, requireContext(),
R.color.light_webview R.color.light_webview
) )
) )
Pair(ContextCompat.getColor(context!!, R.color.light_webview_text), ContextCompat.getColor(context!!, R.color.light_webview)) Pair(ContextCompat.getColor(requireContext(), R.color.light_webview_text), ContextCompat.getColor(requireContext(), R.color.light_webview))
} else { } else {
Pair(null, null) Pair(null, null)
} }
@ -413,14 +417,14 @@ class ArticleFragment : Fragment() {
"#FFFFFF" "#FFFFFF"
} }
rootView!!.webcontent.settings.useWideViewPort = true binding.webcontent.settings.useWideViewPort = true
rootView!!.webcontent.settings.loadWithOverviewMode = true binding.webcontent.settings.loadWithOverviewMode = true
rootView!!.webcontent.settings.javaScriptEnabled = false binding.webcontent.settings.javaScriptEnabled = false
rootView!!.webcontent.webViewClient = object : WebViewClient() { binding.webcontent.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean { override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
if (rootView!!.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
rootView!!.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} }
return true return true
} }
@ -456,13 +460,13 @@ class ArticleFragment : Fragment() {
} }
}) })
rootView!!.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)} binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
rootView!!.webcontent.settings.layoutAlgorithm = binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} else { } else {
rootView!!.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN binding.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
} }
var baseUrl: String? = null var baseUrl: String? = null
@ -491,7 +495,7 @@ class ArticleFragment : Fragment() {
"" ""
} }
rootView!!.webcontent.loadDataWithBaseURL( binding.webcontent.loadDataWithBaseURL(
baseUrl, baseUrl,
"""<html> """<html>
|<head> |<head>
@ -544,15 +548,15 @@ class ArticleFragment : Fragment() {
} }
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) { private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
rootView!!.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
activity!!.openItemUrl( requireActivity().openItemUrl(
allItems, allItems,
pageNumber.toInt(), pageNumber.toInt(),
url, url,
customTabsIntent, customTabsIntent,
true, true,
false, false,
activity!! requireActivity()
) )
} }
@ -574,10 +578,10 @@ class ArticleFragment : Fragment() {
} }
fun performClick(): Boolean { fun performClick(): Boolean {
if (rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
rootView!!.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
val position : Int = allImages.indexOf(rootView!!.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)

View File

@ -4,15 +4,17 @@ import android.os.Bundle
import android.view.* import android.view.*
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.databinding.FragmentImageBinding
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 kotlinx.android.synthetic.main.fragment_image.view.*
class ImageFragment : Fragment() { class ImageFragment : Fragment() {
private lateinit var imageUrl : String private lateinit var imageUrl : String
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
private var _binding: FragmentImageBinding? = null
private val binding get() = _binding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -21,18 +23,24 @@ class ImageFragment : Fragment() {
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view : View = inflater.inflate(R.layout.fragment_image, container, false) _binding = FragmentImageBinding.inflate(inflater, container, false)
val view = binding?.root
view.photoView.visibility = View.VISIBLE binding!!.photoView.visibility = View.VISIBLE
Glide.with(activity) Glide.with(activity)
.asBitmap() .asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(imageUrl) .load(imageUrl)
.into(view.photoView) .into(binding!!.photoView)
return view return view
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object { companion object {
private const val ARG_IMAGE = "imageUrl" private const val ARG_IMAGE = "imageUrl"

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.text.format.DateUtils import android.text.format.DateUtils
import apps.amine.bou.readerforselfoss.api.selfoss.Item import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
import apps.amine.bou.readerforselfoss.dateTimeFormatter
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -22,7 +23,7 @@ fun String.toTextDrawableString(c: Context): String {
fun Item.sourceAndDateText(): String { fun Item.sourceAndDateText(): String {
val formattedDate: String = try { val formattedDate: String = try {
" " + DateUtils.getRelativeTimeSpanString( " " + DateUtils.getRelativeTimeSpanString(
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time, SimpleDateFormat(dateTimeFormatter).parse(this.datetime).time,
Date().time, Date().time,
DateUtils.MINUTE_IN_MILLIS, DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE DateUtils.FORMAT_ABBREV_RELATIVE

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string> <string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string>
<string name="pref_switch_items_caching_on">Els articles es guardaran a la memòria del dispositiu i es podran utilitzar sense connexió.</string> <string name="pref_switch_items_caching_on">Els articles es guardaran a la memòria del dispositiu i es podran utilitzar sense connexió.</string>
<string name="pref_switch_items_caching">Guarda els elements per utilitzar-los sense connexió</string> <string name="pref_switch_items_caching">Guarda els elements per utilitzar-los sense connexió</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Sense connexió!</string> <string name="no_network_connectivity">Sense connexió!</string>
<string name="pref_switch_periodic_refresh">Sincronitza els articles</string> <string name="pref_switch_periodic_refresh">Sincronitza els articles</string>
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string> <string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Nicht verbunden !</string> <string name="no_network_connectivity">Nicht verbunden !</string>
<string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string> <string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string>
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string> <string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string> <string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string>
<string name="pref_switch_items_caching_on">Los artículos se guardarán en la memoria del dispositivo y se utilizarán para el uso sin conexión.</string> <string name="pref_switch_items_caching_on">Los artículos se guardarán en la memoria del dispositivo y se utilizarán para el uso sin conexión.</string>
<string name="pref_switch_items_caching">Guardar elementos para uso sin conexión</string> <string name="pref_switch_items_caching">Guardar elementos para uso sin conexión</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Sin conexión!</string> <string name="no_network_connectivity">Sin conexión!</string>
<string name="pref_switch_periodic_refresh">Sincronizar artículos</string> <string name="pref_switch_periodic_refresh">Sincronizar artículos</string>
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string> <string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string> <string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string>
<string name="pref_switch_items_caching_on">Les articles seront enregistrés et l\'application sera utilisable hors ligne.</string> <string name="pref_switch_items_caching_on">Les articles seront enregistrés et l\'application sera utilisable hors ligne.</string>
<string name="pref_switch_items_caching">Sauvegarder les articles pour une utilisation hors ligne</string> <string name="pref_switch_items_caching">Sauvegarder les articles pour une utilisation hors ligne</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Hors connexion !</string> <string name="no_network_connectivity">Hors connexion !</string>
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string> <string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string> <string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string> <string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string>
<string name="pref_switch_items_caching_on">Os artigos gardaranse na memoria do dispositivo e estarán dispoñibles sen conexión.</string> <string name="pref_switch_items_caching_on">Os artigos gardaranse na memoria do dispositivo e estarán dispoñibles sen conexión.</string>
<string name="pref_switch_items_caching">Gardar elementos para uso sen conexión</string> <string name="pref_switch_items_caching">Gardar elementos para uso sen conexión</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Non conectado!</string> <string name="no_network_connectivity">Non conectado!</string>
<string name="pref_switch_periodic_refresh">Sincronizar artigos</string> <string name="pref_switch_periodic_refresh">Sincronizar artigos</string>
<string name="pref_switch_periodic_refresh_off">Os artigos non se sincronizarán coa aplicación de fondo</string> <string name="pref_switch_periodic_refresh_off">Os artigos non se sincronizarán coa aplicación de fondo</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string> <string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string>
<string name="pref_switch_items_caching_on">文章将被保存到设备内存并可在离线时使用</string> <string name="pref_switch_items_caching_on">文章将被保存到设备内存并可在离线时使用</string>
<string name="pref_switch_items_caching">保存项目以便离线使用</string> <string name="pref_switch_items_caching">保存项目以便离线使用</string>
<string name="pref_switch_update_sources">检查新来源和标签</string>
<string name="pref_switch_update_sources_summary">如果你的服务器接收过多的数据库查询,请禁用此功能。</string>
<string name="no_network_connectivity">未连接!</string> <string name="no_network_connectivity">未连接!</string>
<string name="pref_switch_periodic_refresh">同步文章</string> <string name="pref_switch_periodic_refresh">同步文章</string>
<string name="pref_switch_periodic_refresh_off">文章将不会在后台同步</string> <string name="pref_switch_periodic_refresh_off">文章将不会在后台同步</string>

View File

@ -139,6 +139,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -140,6 +140,8 @@
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string> <string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="no_network_connectivity">Not connected !</string> <string name="no_network_connectivity">Not connected !</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>

View File

@ -34,4 +34,10 @@
android:key="notify_new_items" android:key="notify_new_items"
android:dependency="periodic_refresh" android:dependency="periodic_refresh"
android:title="@string/pref_switch_notify_new_items" /> android:title="@string/pref_switch_notify_new_items" />
<SwitchPreference
android:defaultValue="true"
android:key="update_sources"
android:summary="@string/pref_switch_update_sources_summary"
android:title="@string/pref_switch_update_sources" />
</PreferenceScreen> </PreferenceScreen>

View File

@ -0,0 +1,13 @@
A new RSS reader for <a href="http://selfoss.aditu.de/">selfoss</a>.
What it does:
<ul>
<li>Fetches read, unread, and favorite feeds.</li>
<li>Marking as read, marking as favorite.</li>
<li>Manage selfoss sources from the app.</li>
<li>Add an RSS feed from within the app, or by sharing a link from your browser.</li>
<li>Choose between multiple light and dark themes.</li>
</ul>
PS: It only works with Selfoss

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 422 KiB

View File

@ -0,0 +1,3 @@
A new RSS reader for <a href="http://selfoss.aditu.de/">selfoss</a>.
It connects to your selfoss instance (works only with selfoss, and can't work without it), and you'll be able to read and manage all your RSS feeds.

View File

@ -0,0 +1 @@
Reader for Selfoss