Compare commits

..

1 Commits

Author SHA1 Message Date
edfea45936 Migration 2021-10-27 15:22:35 +02:00
77 changed files with 1285 additions and 893 deletions

View File

@ -44,12 +44,6 @@
- Closing #236. New sources can be added in Selfoss 2.19.
- Closing #397 and #355. Tag and Sources filters are now exclusive.
- Dropped support for android 4, the last version supporting it is v1721030811
- Added ability to scroll articles up and down using the volume keys #400
**1.6.x**
- Handling hidden tags.

View File

@ -1 +1,47 @@
# Project moved to https://github.com/aminecmi/ReaderforSelfoss-multiplatform
# ReaderForSelfoss **(Only available from F-Droid)**
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
**The project is not dead at all.**
I still want to work on it, but for the last few months, I didn't have that much time to do so.
If you are a developer, don't hesitate to help with PRs.
If you are a user, you can still create new issues. I'll fix them when I can.
<a href="https://f-droid.org/packages/apps.amine.bou.readerforselfoss"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
## Screen captures
<img src="res//fr-card.png?raw=true" alt="card view" width="400"/> <img src="res//fr-list.png?raw=true" alt="list view" width="400"/>
## Like my app ?
<a href="https://www.buymeacoffee.com/aminecmi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
## Want to help ?
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
2. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md).
3. Build the project by following [these steps](https://github.com/aminecmi/ReaderforSelfoss/blob/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
## Useful links
- [Check what changed](https://github.com/aminecmi/ReaderforSelfoss/blob/master/CHANGELOG.md)
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1)
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues)
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
## Contributors (Alphabetical order) ❤️
- [@aancel](https://github.com/aancel)
- [@Binnette](https://github.com/Binnette)
- [@davidoskky](https://github.com/davidoskky)
- [@hectorgabucio](https://github.com/hectorgabucio)
- [@licaon-kter](https://github.com/licaon-kter)
- [@sergey-babkin](https://github.com/sergey-babkin)

View File

@ -32,9 +32,6 @@ apply plugin: 'kotlin-kapt'
android {
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@ -45,7 +42,7 @@ android {
}
defaultConfig {
applicationId "apps.amine.bou.readerforselfoss"
minSdkVersion 21
minSdkVersion 16
targetSdkVersion 31
versionCode versionCodeFromGit()
versionName versionNameFromGit()
@ -94,8 +91,6 @@ android {
}
dependencies {
implementation 'androidx.preference:preference-ktx:1.1.1'
// Testing
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0-alpha02'
androidTestImplementation 'androidx.test:runner:1.3.1-alpha02'
@ -104,41 +99,39 @@ dependencies {
// Espresso-intents for validation and stubbing of Intents
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0-alpha02'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// Android Support
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation "androidx.appcompat:appcompat:1.4.0-beta01"
implementation 'com.google.android.material:material:1.5.0-alpha04'
implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01'
implementation "androidx.legacy:legacy-support-v4:$android_version"
implementation 'androidx.vectordrawable:vectordrawable:1.2.0-alpha02'
implementation 'androidx.browser:browser:1.4.0'
implementation "androidx.browser:browser:1.3.0"
implementation "androidx.cardview:cardview:$android_version"
implementation 'androidx.annotation:annotation:1.3.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'org.jsoup:jsoup:1.14.3'
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
implementation "androidx.annotation:annotation:1.2.0"
implementation 'androidx.work:work-runtime-ktx:2.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'org.jsoup:jsoup:1.13.1'
//multidex
implementation 'androidx.multidex:multidex:2.0.1'
// About
implementation 'com.mikepenz:aboutlibraries-core:8.9.4'
implementation 'com.mikepenz:aboutlibraries:8.9.4'
implementation "com.mikepenz:aboutlibraries-definitions:8.9.4"
implementation('com.mikepenz:aboutlibraries:6.2.0@aar') {
transitive = true
}
// Async
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
// Retrofit + http logging + okhttp
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.burgstaller:okhttp-digest:2.5'
// Material-ish things
implementation 'com.ashokvarma.android:bottom-navigation-bar:2.2.0'
implementation 'com.ashokvarma.android:bottom-navigation-bar:2.1.0'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
// glide
@ -146,24 +139,23 @@ dependencies {
implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
// Drawer
implementation 'com.mikepenz:materialdrawer:8.4.5'
implementation 'com.mikepenz:materialdrawer:8.4.4'
// Themes
implementation 'com.52inc:scoops:1.0.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'com.jaredrummler:colorpicker:1.0.2'
implementation 'com.github.rubensousa:floatingtoolbar:1.5.1'
// Pager
implementation 'me.relex:circleindicator:2.1.6'
implementation "androidx.viewpager2:viewpager2:1.1.0-beta01"
implementation 'me.relex:circleindicator:2.0.0@aar'
//PhotoView
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.core:core-ktx:1.7.0-rc01'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.4.0'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-rc01"
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0-rc01"
implementation "androidx.room:room-ktx:2.4.0-beta01"
kapt "androidx.room:room-compiler:2.4.0-beta01"

View File

@ -7,20 +7,23 @@ import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.pressBack
import androidx.test.espresso.action.ViewActions.pressKey
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.times
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import android.view.KeyEvent
import androidx.test.espresso.matcher.RootMatchers.isDialog
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
import org.junit.Before
@ -81,14 +84,11 @@ class HomeActivityEspressoTest {
onView(withMenu(id = R.id.refresh, titleId = R.string.menu_home_refresh))
.perform(click())
onView(withText(android.R.string.ok))
.inRoot(isDialog()).check(matches(isDisplayed())).perform(click())
openActionBarOverflowOrOptionsMenu(context)
onView(withText(R.string.action_disconnect)).perform(click())
intended(hasComponent(LoginActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name), times(1))
}
// TODO: test articles opening and actions for cards and lists

View File

@ -85,7 +85,7 @@ class LoginActivityEspressoTest {
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
}
// TODO: Add tests for multiple false urls with dialog
@ -101,19 +101,19 @@ class LoginActivityEspressoTest {
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginView)).perform(click()).perform(
typeText(username),
closeSoftKeyboard()
)
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.passwordView)).check(
onView(withId(R.id.passwordLayout)).check(
matches(
isHintOrErrorEnabled()
)
@ -141,9 +141,9 @@ class LoginActivityEspressoTest {
onView(withId(R.id.signInButton)).perform(click())
onView(withId(R.id.urlView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordView)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.urlLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.loginLayout)).check(matches(isHintOrErrorEnabled()))
onView(withId(R.id.passwordLayout)).check(matches(isHintOrErrorEnabled()))
}
@Test
@ -167,7 +167,6 @@ class LoginActivityEspressoTest {
onView(withId(R.id.signInButton)).perform(click())
Thread.sleep(2000)
intended(hasComponent(HomeActivity::class.java.name))
}

View File

@ -1,6 +1,5 @@
package apps.amine.bou.readerforselfoss
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.preference.PreferenceManager
@ -11,7 +10,6 @@ import androidx.test.espresso.intent.Intents.times
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.rule.ActivityTestRule
import androidx.test.runner.AndroidJUnit4
import apps.amine.bou.readerforselfoss.utils.Config
import org.junit.After
import org.junit.Before
@ -24,9 +22,6 @@ class MainActivityEspressoTest {
lateinit var intent: Intent
lateinit var preferencesEditor: SharedPreferences.Editor
private lateinit var url: String
private lateinit var username: String
private lateinit var password: String
@Rule @JvmField
val rule = ActivityTestRule(MainActivity::class.java, true, false)
@ -37,39 +32,31 @@ class MainActivityEspressoTest {
val context = getInstrumentation().targetContext
// create a SharedPreferences editor
preferencesEditor = context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE).edit()
url = BuildConfig.LOGIN_URL
username = BuildConfig.LOGIN_USERNAME
password = BuildConfig.LOGIN_PASSWORD
preferencesEditor = PreferenceManager.getDefaultSharedPreferences(context).edit()
Intents.init()
}
@Test
fun checkFirstOpenLaunchesIntro() {
preferencesEditor.putString("url", "")
preferencesEditor.putString("password", "")
preferencesEditor.putString("login", "")
preferencesEditor.commit()
rule.launchActivity(intent)
intended(hasComponent(LoginActivity::class.java.name))
intended(hasComponent(HomeActivity::class.java.name), times(0))
}
@Test
fun checkNotFirstOpenLaunchesLogin() {
preferencesEditor.putString("url", url)
preferencesEditor.putString("password", password)
preferencesEditor.putString("login", username)
preferencesEditor.putBoolean("firstStart", true)
preferencesEditor.commit()
rule.launchActivity(intent)
intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(HomeActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name), times(0))
}
@Test
fun checkNotFirstOpenLaunchesLogin() {
preferencesEditor.putBoolean("firstStart", false)
preferencesEditor.commit()
rule.launchActivity(intent)
intended(hasComponent(MainActivity::class.java.name))
intended(hasComponent(LoginActivity::class.java.name))
}
@After

View File

@ -1,8 +1,8 @@
package apps.amine.bou.readerforselfoss
import com.google.android.material.textfield.TextInputLayout
import androidx.test.espresso.matcher.ViewMatchers
import android.view.View
import android.widget.EditText
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
@ -14,11 +14,11 @@ fun isHintOrErrorEnabled(): Matcher<View> =
}
override fun matchesSafely(item: View?): Boolean {
if (item !is EditText) {
if (item !is TextInputLayout) {
return false
}
return item.error.isNotEmpty()
return item.isHintEnabled || item.isErrorEnabled
}
}

View File

@ -1,29 +0,0 @@
package apps.amine.bou.readerforselfoss.utils
import org.junit.Test
class DateUtilsTest {
@Test
fun parseDateV4() {
Config.apiVersion = 4
val dateString = "2013-04-07T13:43:00+01:00"
val milliseconds = parseDate(dateString).toEpochMilli()
val correctMilliseconds : Long = 1365338580000
assert(milliseconds == correctMilliseconds)
}
@Test
fun parseDateV1() {
Config.apiVersion = 0
val dateString = "2013-04-07 13:43:00"
val milliseconds = parseDate(dateString).toEpochMilli()
val correctMilliseconds = 1365342180000
assert(milliseconds == correctMilliseconds)
}
}

View File

@ -2,8 +2,9 @@ package apps.amine.bou.readerforselfoss
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.appcompat.app.AppCompatActivity
import android.view.View
@ -49,24 +50,38 @@ class AddSourceActivity : AppCompatActivity() {
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
}
val drawable = binding.nameInput.background
drawable.setTint(appColors.colorAccent)
drawable.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
// TODO: clean
if(Build.VERSION.SDK_INT > 16) {
binding.nameInput.background = drawable
} else{
binding.nameInput.setBackgroundDrawable(drawable)
}
val drawable1 = binding.sourceUri.background
drawable1.setTint(appColors.colorAccent)
drawable1.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
if(Build.VERSION.SDK_INT > 16) {
binding.sourceUri.background = drawable1
} else{
binding.sourceUri.setBackgroundDrawable(drawable1)
}
val drawable2 = binding.tags.background
drawable2.setTint(appColors.colorAccent)
drawable2.setColorFilter(appColors.colorAccent, PorterDuff.Mode.SRC_ATOP)
if(Build.VERSION.SDK_INT > 16) {
binding.tags.background = drawable2
} else{
binding.tags.setBackgroundDrawable(drawable2)
}
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)

View File

@ -7,8 +7,9 @@ import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -18,6 +19,7 @@ import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat
import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout
import androidx.recyclerview.widget.*
@ -99,7 +101,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var displayAllCount = false
private var fullHeightCards: Boolean = false
private var itemsNumber: Int = 200
private var elementsShown: Int = 1
private var elementsShown: Int = 0
private var maybeTagFilter: Tag? = null
private var maybeSourceFilter: Source? = null
private var maybeSearchFilter: String? = null
private var userIdentifier: String = ""
private var displayAccountHeader: Boolean = false
private var infiniteScroll: Boolean = false
@ -166,8 +171,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
handleThemeBinding()
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setHomeButtonEnabled(true)
val mDrawerToggle = ActionBarDrawerToggle(this, binding.drawerContainer, binding.toolBar, R.string.material_drawer_open, R.string.material_drawer_close)
binding.drawerContainer.addDrawerListener(mDrawerToggle)
mDrawerToggle.syncState()
@ -216,7 +221,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
lastFetchDone = false
handleDrawerItems()
CoroutineScope(Dispatchers.Main).launch {
refreshFocusedItems(applicationContext, api, db, itemsNumber)
refreshFocusedItems(applicationContext, api, db)
getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false
}
@ -247,7 +252,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
val position = viewHolder.bindingAdapterPosition
val position = viewHolder.adapterPosition
val i = items.elementAtOrNull(position)
if (i != null) {
@ -335,7 +340,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun getApiMajorVersion() {
api.apiVersion.enqueue(object : Callback<ApiVersion> {
override fun onFailure(call: Call<ApiVersion>, t: Throwable) {
Config.apiVersion = apiVersionMajor
if (apiVersionMajor >= 4) {
Config.dateTimeFormatter = "yyyy-MM-dd'T'HH:mm:ssXXX"
}
}
override fun onResponse(call: Call<ApiVersion>, response: Response<ApiVersion>) {
@ -344,7 +351,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
apiVersionMajor = version.getApiMajorVersion()
sharedPref.edit().putInt("apiVersionMajor", apiVersionMajor).apply()
Config.apiVersion = apiVersionMajor
if (apiVersionMajor >= 4) {
Config.dateTimeFormatter = "yyyy-MM-dd'T'HH:mm:ssXXX"
}
}
}
})
@ -431,16 +440,20 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private fun handleThemeBinding() {
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
}
}
private fun handleThemeUpdate() {
val scoop = Scoop.getInstance()
scoop.update(Toppings.PRIMARY.value, appColors.colorPrimary)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.update(Toppings.PRIMARY_DARK.value, appColors.colorPrimaryDark)
}
}
private fun handleDrawer() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
@ -501,7 +514,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
iconRes = R.drawable.ic_settings_black_24dp
isIconTinted = true
onDrawerItemClickListener = { _, _, _ ->
startActivity(Intent(this@HomeActivity, SettingsActivity::class.java))
startActivityForResult(
Intent(
this@HomeActivity,
SettingsActivity::class.java
),
MENU_PREFERENCES
)
false
}
})
@ -559,9 +578,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
color = ColorHolder.fromColor(appColors.colorAccent) }
onDrawerItemClickListener = { _,_,_ ->
allItems = ArrayList()
maybeTagFilter = it
SharedItems.tagFilter = it.tag
SharedItems.sourceFilter = null
SharedItems.sourceIDFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -612,9 +630,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
color = ColorHolder.fromColor(appColors.colorAccent) }
onDrawerItemClickListener = { _,_,_ ->
allItems = ArrayList()
maybeTagFilter = it
SharedItems.tagFilter = it.tag
SharedItems.sourceFilter = null
SharedItems.sourceIDFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -648,9 +665,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
iconUrl = source.getIcon(this@HomeActivity)
onDrawerItemClickListener = { _,_,_ ->
allItems = ArrayList()
maybeSourceFilter = source
SharedItems.sourceIDFilter = source.id.toLong()
SharedItems.sourceFilter = source.title
SharedItems.tagFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -671,10 +688,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
badgeRes = R.string.drawer_action_clear
onDrawerItemClickListener = { _,_,_ ->
allItems = ArrayList()
maybeSourceFilter = null
SharedItems.sourceFilter = null
SharedItems.sourceIDFilter = null
maybeTagFilter = null
SharedItems.tagFilter = null
binding.mainDrawer.setSelectionAtPosition(-1)
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -981,7 +999,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
CoroutineScope(Dispatchers.Main).launch {
if (appendResults || !SharedItems.fetchedUnread) {
binding.swipeRefreshLayout.isRefreshing = true
getUnreadItems(applicationContext, api, db, itemsNumber, offset)
getUnreadItems(applicationContext, api, db, offset)
binding.swipeRefreshLayout.isRefreshing = false
}
SharedItems.getUnRead()
@ -994,7 +1012,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
CoroutineScope(Dispatchers.Main).launch {
if (appendResults || !SharedItems.fetchedAll) {
binding.swipeRefreshLayout.isRefreshing = true
getReadItems(applicationContext, api, db, itemsNumber, offset)
getReadItems(applicationContext, api, db, offset)
binding.swipeRefreshLayout.isRefreshing = false
}
SharedItems.getAll()
@ -1007,7 +1025,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
CoroutineScope(Dispatchers.Main).launch {
if (appendResults || !SharedItems.fetchedStarred) {
binding.swipeRefreshLayout.isRefreshing = true
getStarredItems(applicationContext, api, db, itemsNumber, offset)
getStarredItems(applicationContext, api, db, offset)
binding.swipeRefreshLayout.isRefreshing = false
}
SharedItems.getStarred()
@ -1119,6 +1137,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
override fun onQueryTextChange(p0: String?): Boolean {
if (p0.isNullOrBlank()) {
maybeSearchFilter = null
SharedItems.searchFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
@ -1127,18 +1146,29 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}
override fun onQueryTextSubmit(p0: String?): Boolean {
maybeSearchFilter = p0
SharedItems.searchFilter = p0
getElementsAccordingToTab()
fetchOnEmptyList()
return false
}
override fun onActivityResult(req: Int, result: Int, data: Intent?) {
when (req) {
MENU_PREFERENCES -> {
//drawer.closeDrawer()
recreate()
}
else -> super.onActivityResult(req, result, data)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.home_menu, menu)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.getActionView() as SearchView
val searchView = MenuItemCompat.getActionView(searchItem) as SearchView
searchView.setOnQueryTextListener(this)
return true
@ -1253,7 +1283,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
.addTag("selfoss-loading")
.build()
WorkManager.getInstance(baseContext).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
WorkManager.getInstance().enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
}
}

View File

@ -3,9 +3,8 @@ package apps.amine.bou.readerforselfoss
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding
import apps.amine.bou.readerforselfoss.fragments.ImageFragment
@ -29,8 +28,8 @@ class ImageActivity : AppCompatActivity() {
allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
position = intent.getIntExtra("position", 0)
binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(position, false)
binding.pager.adapter = ScreenSlidePagerAdapter(supportFragmentManager)
binding.pager.currentItem = position
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -44,10 +43,14 @@ class ImageActivity : AppCompatActivity() {
return super.onOptionsItemSelected(item)
}
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItemCount(): Int = allImages.size
override fun getCount(): Int {
return allImages.size
}
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
override fun getItem(position: Int): ImageFragment {
return ImageFragment.newInstance(allImages[position])
}
}
}

View File

@ -14,13 +14,15 @@ import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.preference.PreferenceManager
import android.widget.Toast
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
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.utils.Config
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import retrofit2.Call
import retrofit2.Callback
@ -52,7 +54,7 @@ class LoginActivity : AppCompatActivity() {
handleBaseUrlFail()
settings = PreferenceManager.getDefaultSharedPreferences(applicationContext)
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
userIdentifier = settings.getString("unique_id", "")!!
editor = settings.edit()
@ -89,16 +91,16 @@ class LoginActivity : AppCompatActivity() {
isWithLogin = !isWithLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
binding.loginView.visibility = visi
binding.passwordView.visibility = visi
binding.loginLayout.visibility = visi
binding.passwordLayout.visibility = visi
}
binding.withHttpLogin.setOnCheckedChangeListener { _, b ->
isWithHTTPLogin = !isWithHTTPLogin
val visi: Int = if (b) View.VISIBLE else View.GONE
binding.httpLoginView.visibility = visi
binding.httpPasswordView.visibility = visi
binding.httpLoginInput.visibility = visi
binding.httpPasswordInput.visibility = visi
}
}
@ -109,8 +111,9 @@ class LoginActivity : AppCompatActivity() {
alertDialog.setMessage(getString(R.string.base_url_error))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
) { dialog, _ -> dialog.dismiss() }
"OK",
{ dialog, _ -> dialog.dismiss() }
)
alertDialog.show()
}
}
@ -151,8 +154,9 @@ class LoginActivity : AppCompatActivity() {
alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
) { dialog, _ -> dialog.dismiss() }
"OK",
{ dialog, _ -> dialog.dismiss() }
)
alertDialog.show()
inValidCount = 0
}

View File

@ -2,7 +2,9 @@ package apps.amine.bou.readerforselfoss
import android.content.Intent
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity
import apps.amine.bou.readerforselfoss.databinding.ActivityAddSourceBinding
import apps.amine.bou.readerforselfoss.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {

View File

@ -6,7 +6,7 @@ import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import android.widget.ImageView
import androidx.multidex.MultiDexApplication
import apps.amine.bou.readerforselfoss.utils.Config

View File

@ -3,17 +3,21 @@ package apps.amine.bou.readerforselfoss
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.core.content.ContextCompat
import androidx.viewpager.widget.ViewPager
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import android.view.ViewGroup
import androidx.room.Room
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.databinding.ActivityReaderBinding
@ -24,17 +28,18 @@ import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3
import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4
import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.themes.Toppings
import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.SharedItems
import apps.amine.bou.readerforselfoss.utils.toggleStar
import com.ftinc.scoop.Scoop
import me.relex.circleindicator.CircleIndicator
class ReaderActivity : AppCompatActivity() {
private var markOnScroll: Boolean = false
private var currentItem: Int = 0
private lateinit var userIdentifier: String
private lateinit var appColors: AppColors
private lateinit var api: SelfossApi
@ -45,14 +50,14 @@ class ReaderActivity : AppCompatActivity() {
private lateinit var binding: ActivityReaderBinding
private var activeAlignment: Int = 1
private val JUSTIFY = 1
private val ALIGN_LEFT = 2
val JUSTIFY = 1
val ALIGN_LEFT = 2
private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE)
toolbarMenu.findItem(R.id.star).icon.colorFilter = PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
} else {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED)
toolbarMenu.findItem(R.id.star).icon.colorFilter = PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP)
}
}
@ -68,7 +73,6 @@ class ReaderActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appColors = AppColors(this)
binding = ActivityReaderBinding.inflate(layoutInflater)
val view = binding.root
@ -81,7 +85,9 @@ class ReaderActivity : AppCompatActivity() {
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
}
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -112,80 +118,23 @@ class ReaderActivity : AppCompatActivity() {
readItem(allItems[currentItem])
binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(currentItem, false)
binding.pager.adapter =
ScreenSlidePagerAdapter(supportFragmentManager, AppColors(this@ReaderActivity))
binding.pager.currentItem = currentItem
}
override fun onResume() {
super.onResume()
binding.indicator.setViewPager(binding.pager)
}
notifyAdapter()
private fun readItem(item: Item) {
if (markOnScroll) {
SharedItems.readItem(applicationContext, api, db, item)
}
}
binding.pager.setPageTransformer(true, DepthPageTransformer())
(binding.indicator as CircleIndicator).setViewPager(binding.pager)
override fun onSaveInstanceState(oldInstanceState: Bundle) {
super.onSaveInstanceState(oldInstanceState)
oldInstanceState.clear()
}
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
FragmentStateAdapter(fa) {
override fun getItemCount(): Int = allItems.size
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
currentFragment.scrollDown()
true
}
KeyEvent.KEYCODE_VOLUME_UP -> {
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
currentFragment.scrollUp()
true
}
else -> {
super.onKeyDown(keyCode, event)
}
}
}
private fun alignmentMenu(showJustify: Boolean) {
toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify
toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu
if (allItems.isNotEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
if (activeAlignment == JUSTIFY) {
alignmentMenu(false)
} else {
alignmentMenu(true)
}
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
binding.pager.addOnPageChangeListener(
object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (allItems[position].starred) {
canRemoveFromFavorite()
@ -196,6 +145,73 @@ class ReaderActivity : AppCompatActivity() {
}
}
)
}
private fun readItem(item: Item) {
if (markOnScroll) {
SharedItems.readItem(applicationContext, api, db, item)
}
}
private fun notifyAdapter() {
(binding.pager.adapter as ScreenSlidePagerAdapter).notifyDataSetChanged()
}
override fun onPause() {
super.onPause()
if (markOnScroll) {
binding.pager.clearOnPageChangeListeners()
}
}
override fun onSaveInstanceState(oldInstanceState: Bundle) {
super.onSaveInstanceState(oldInstanceState)
oldInstanceState.clear()
}
private inner class ScreenSlidePagerAdapter(fm: FragmentManager, val appColors: AppColors) :
FragmentStatePagerAdapter(fm) {
override fun getCount(): Int {
return allItems.size
}
override fun getItem(position: Int): ArticleFragment {
return ArticleFragment.newInstance(position, allItems)
}
override fun startUpdate(container: ViewGroup) {
super.startUpdate(container)
container.background = ColorDrawable(
ContextCompat.getColor(
this@ReaderActivity,
appColors.colorBackground
)
)
}
}
fun alignmentMenu(showJustify: Boolean) {
toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify
toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu
if (!allItems.isEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
if (activeAlignment == JUSTIFY) {
alignmentMenu(false)
} else {
alignmentMenu(true)
}
return true
}
@ -204,11 +220,13 @@ class ReaderActivity : AppCompatActivity() {
fun afterSave() {
allItems[binding.pager.currentItem] =
allItems[binding.pager.currentItem].toggleStar()
notifyAdapter()
canRemoveFromFavorite()
}
fun afterUnsave() {
allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem].toggleStar()
notifyAdapter()
canFavorite()
}

View File

@ -3,14 +3,16 @@ package apps.amine.bou.readerforselfoss
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import android.widget.Toast
import apps.amine.bou.readerforselfoss.adapters.SourcesListAdapter
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
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.Toppings
@ -28,16 +30,18 @@ class SourcesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@SourcesActivity)
super.onCreate(savedInstanceState)
binding = ActivitySourcesBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
super.onCreate(savedInstanceState)
setContentView(view)
}
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -71,7 +75,7 @@ class SourcesActivity : AppCompatActivity() {
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = mLayoutManager
if (this@SourcesActivity.isNetworkAccessible(binding.recyclerView)) {
if (this@SourcesActivity.isNetworkAccessible(this@SourcesActivity.findViewById(R.id.recyclerView))) {
api.sources.enqueue(object : Callback<List<Source>> {
override fun onResponse(
call: Call<List<Source>>,

View File

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView.ScaleType
import androidx.core.content.ContextCompat
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
@ -60,6 +61,10 @@ class ItemCardAdapter(
binding.favButton.isSelected = itm.starred
binding.title.text = itm.getTitleDecoded()
binding.title.setTextColor(ContextCompat.getColor(
c,
appColors.textColor
))
binding.title.setOnTouchListener(LinkOnTouchListener())
@ -67,6 +72,11 @@ class ItemCardAdapter(
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
binding.sourceTitleAndDate.setTextColor(ContextCompat.getColor(
c,
appColors.textColor
))
if (!fullHeightCards) {
binding.itemImage.maxHeight = imageMaxHeight
binding.itemImage.scaleType = ScaleType.CENTER_CROP
@ -102,6 +112,7 @@ class ItemCardAdapter(
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setCardBackgroundColor(appColors.cardBackgroundColor)
handleClickListeners()
handleCustomTabActions()
}

View File

@ -5,6 +5,7 @@ import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.databinding.ListItemBinding
@ -48,14 +49,25 @@ class ItemListAdapter(
with(holder) {
val itm = items[position]
binding.title.text = itm.getTitleDecoded()
binding.title.setTextColor(ContextCompat.getColor(
c,
appColors.textColor
))
binding.title.setOnTouchListener(LinkOnTouchListener())
binding.title.setLinkTextColor(appColors.colorAccent)
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
binding.sourceTitleAndDate.setTextColor(ContextCompat.getColor(
c,
appColors.textColor
))
if (itm.getThumbnail(c).isEmpty()) {
if (itm.getIcon(c).isEmpty()) {

View File

@ -39,13 +39,13 @@ suspend fun updateItems(context: Context, api: SelfossApi, db: AppDatabase) = co
}
}
suspend fun refreshFocusedItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int) = withContext(Dispatchers.IO) {
suspend fun refreshFocusedItems(context: Context, api: SelfossApi, db: AppDatabase) = withContext(Dispatchers.IO) {
if (isNetworkAvailable(context)) {
val response = when (SharedItems.displayedItems) {
"read" -> api.readItems(itemsNumber, 0)
"unread" -> api.newItems(itemsNumber, 0)
"starred" -> api.starredItems(itemsNumber, 0)
else -> api.readItems(itemsNumber, 0)
"read" -> api.readItems(200, 0)
"unread" -> api.newItems(200, 0)
"starred" -> api.starredItems(200, 0)
else -> api.readItems(200, 0)
}
if (response.isSuccessful) {
@ -55,33 +55,33 @@ suspend fun refreshFocusedItems(context: Context, api: SelfossApi, db: AppDataba
}
}
suspend fun getReadItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) {
suspend fun getReadItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) {
if (isNetworkAvailable(context)) {
try {
enqueueArticles(api.readItems( itemsNumber, offset), db, false)
enqueueArticles(api.readItems( 200, offset), db, false)
SharedItems.fetchedAll = true
SharedItems.updateDatabase(db)
} catch (e: Throwable) {}
}
}
suspend fun getUnreadItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) {
suspend fun getUnreadItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) {
if (isNetworkAvailable(context)) {
try {
if (!SharedItems.fetchedUnread) {
SharedItems.clearDBItems(db)
}
enqueueArticles(api.newItems(itemsNumber, offset), db, false)
enqueueArticles(api.newItems(200, offset), db, false)
SharedItems.fetchedUnread = true
} catch (e: Throwable) {}
}
SharedItems.updateDatabase(db)
}
suspend fun getStarredItems(context: Context, api: SelfossApi, db: AppDatabase, itemsNumber: Int, offset: Int) = withContext(Dispatchers.IO) {
suspend fun getStarredItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) {
if (isNetworkAvailable(context)) {
try {
enqueueArticles(api.starredItems(itemsNumber, offset), db, false)
enqueueArticles(api.starredItems(200, offset), db, false)
SharedItems.fetchedStarred = true
SharedItems.updateDatabase(db)
} catch (e: Throwable) {

View File

@ -14,8 +14,6 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.google.gson.annotations.SerializedName
import java.util.*
import kotlin.collections.ArrayList
private fun constructUrl(config: Config?, path: String, file: String?): String {
return if (file.isEmptyOrNullOrNullString()) {
@ -157,14 +155,14 @@ data class Item(
}
fun getImages() : ArrayList<String> {
val allImages = ArrayList<String>()
var allImages = ArrayList<String>()
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
val url = image.attr("src")
if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg") ||
url.lowercase(Locale.US).contains(".png") ||
url.lowercase(Locale.US).contains(".webp"))
if (url.toLowerCase().contains(".jpg") ||
url.toLowerCase().contains(".jpeg") ||
url.toLowerCase().contains(".png") ||
url.toLowerCase().contains(".webp"))
{
allImages.add(url)
}

View File

@ -5,7 +5,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationCompat.PRIORITY_LOW

View File

@ -9,16 +9,18 @@ import android.graphics.Bitmap
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceManager
import android.preference.PreferenceManager
import android.view.*
import android.webkit.*
import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import com.google.android.material.floatingactionbutton.FloatingActionButton
import androidx.fragment.app.Fragment
import androidx.core.content.ContextCompat
import androidx.core.widget.NestedScrollView
import androidx.appcompat.app.AlertDialog
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.res.ResourcesCompat
import androidx.room.Room
import apps.amine.bou.readerforselfoss.ImageActivity
@ -52,8 +54,9 @@ import java.util.concurrent.ExecutionException
import kotlin.collections.ArrayList
class ArticleFragment : Fragment() {
private lateinit var pageNumber: Number
private var fontSize: Int = 16
private lateinit var item: Item
private lateinit var allItems: ArrayList<Item>
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
private lateinit var url: String
private lateinit var contentText: String
@ -90,7 +93,8 @@ class ArticleFragment : Fragment() {
super.onCreate(savedInstanceState)
item = requireArguments().getParcelable(ARG_ITEMS)!!
pageNumber = requireArguments().getInt(ARG_POSITION)
allItems = requireArguments().getParcelableArrayList<Item>(ARG_ITEMS) as ArrayList<Item>
db = Room.databaseBuilder(
requireContext(),
@ -106,12 +110,12 @@ class ArticleFragment : Fragment() {
try {
_binding = FragmentArticleBinding.inflate(inflater, container, false)
url = item.getLinkDecoded()
contentText = item.content
contentTitle = item.getTitleDecoded()
contentImage = item.getThumbnail(requireActivity())
contentSource = item.sourceAndDateText()
allImages = item.getImages()
url = allItems[pageNumber.toInt()].getLinkDecoded()
contentText = allItems[pageNumber.toInt()].content
contentTitle = allItems[pageNumber.toInt()].getTitleDecoded()
contentImage = allItems[pageNumber.toInt()].getThumbnail(requireActivity())
contentSource = allItems[pageNumber.toInt()].sourceAndDateText()
allImages = allItems[pageNumber.toInt()].getImages()
prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit()
@ -163,16 +167,24 @@ class ArticleFragment : Fragment() {
when (item.itemId) {
R.id.more_action -> getContentFromMercury(customTabsIntent)
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.open_action -> requireActivity().openItemUrl(
allItems,
pageNumber.toInt(),
url,
customTabsIntent,
false,
false,
requireActivity()
)
R.id.unread_action -> if (context != null) {
if (this@ArticleFragment.item.unread) {
if (allItems[pageNumber.toInt()].unread) {
SharedItems.readItem(
context!!,
api,
db,
this@ArticleFragment.item
allItems[pageNumber.toInt()]
)
this@ArticleFragment.item.unread = false
allItems[pageNumber.toInt()].unread = false
Toast.makeText(
context,
R.string.marked_as_read,
@ -183,9 +195,9 @@ class ArticleFragment : Fragment() {
context!!,
api,
db,
this@ArticleFragment.item
allItems[pageNumber.toInt()]
)
this@ArticleFragment.item.unread = true
allItems[pageNumber.toInt()].unread = true
Toast.makeText(
context,
R.string.marked_as_unread,
@ -282,7 +294,9 @@ class ArticleFragment : Fragment() {
}
}
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
private fun getContentFromMercury(
customTabsIntent: CustomTabsIntent
) {
if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) {
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
@ -332,31 +346,19 @@ class ArticleFragment : Fragment() {
} else {
binding.imageView.visibility = View.GONE
}
} catch (e: Exception) {
if (context != null) {
}
}
} catch (e: Exception) { }
try {
binding.nestedScrollView.scrollTo(0, 0)
binding.progressBar.visibility = View.GONE
} catch (e: Exception) {
if (context != null) {
}
}
} catch (e: Exception) { }
} else {
try {
openInBrowserAfterFailing(customTabsIntent)
} catch (e: Exception) {
if (context != null) {
}
}
}
} catch (e: Exception) {
if (context != null) {
}
} catch (e: Exception) { }
}
} catch (e: Exception) { }
}
override fun onFailure(
@ -377,12 +379,42 @@ class ArticleFragment : Fragment() {
binding.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE
// TODO: Set the color strings programmatically
val (stringTextColor, stringBackgroundColor) = if (appColors.isDarkTheme) {
Pair("#FFFFFF", "#303030")
val (textColor, backgroundColor) = if (appColors.isDarkTheme) {
if (context != null) {
binding.webcontent.setBackgroundColor(
ContextCompat.getColor(
requireContext(),
R.color.dark_webview
)
)
Pair(ContextCompat.getColor(requireContext(), R.color.dark_webview_text), ContextCompat.getColor(requireContext(), R.color.dark_webview))
} else {
Pair("#212121", "#FAFAFA")
Pair(null, null)
}
} else {
if (context != null) {
binding.webcontent.setBackgroundColor(
ContextCompat.getColor(
requireContext(),
R.color.light_webview
)
)
Pair(ContextCompat.getColor(requireContext(), R.color.light_webview_text), ContextCompat.getColor(requireContext(), R.color.light_webview))
} else {
Pair(null, null)
}
}
val stringTextColor: String = if (textColor != null) {
String.format("#%06X", 0xFFFFFF and textColor)
} else {
"#000000"
}
val stringBackgroundColor = if (backgroundColor != null) {
String.format("#%06X", 0xFFFFFF and backgroundColor)
} else {
"#FFFFFF"
}
binding.webcontent.settings.useWideViewPort = true
@ -399,22 +431,27 @@ class ArticleFragment : Fragment() {
override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
if (url.lowercase(Locale.ROOT).contains(".jpg") || url.lowercase(Locale.ROOT).contains(".jpeg")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
}catch ( e : ExecutionException) {}
}
else if (url.lowercase(Locale.US).contains(".png")) {
else if (url.lowercase(Locale.ROOT).contains(".png")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
}catch ( e : ExecutionException) {}
}
else if (url.lowercase(Locale.US).contains(".webp")) {
else if (url.lowercase(Locale.ROOT).contains(".webp")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
val compressFormat = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Bitmap.CompressFormat.WEBP_LOSSLESS
} else {
Bitmap.CompressFormat.WEBP
}
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, compressFormat))
}catch ( e : ExecutionException) {}
}
@ -430,8 +467,12 @@ class ArticleFragment : Fragment() {
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} else {
binding.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.SINGLE_COLUMN
}
var baseUrl: String? = null
@ -511,34 +552,31 @@ class ArticleFragment : Fragment() {
)
}
fun scrollDown() {
val height = binding.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, height/2)
}
fun scrollUp() {
val height = binding.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, -height/2)
}
private fun openInBrowserAfterFailing(customTabsIntent: CustomTabsIntent) {
binding.progressBar.visibility = View.GONE
requireActivity().openItemUrlInternalBrowser(
requireActivity().openItemUrl(
allItems,
pageNumber.toInt(),
url,
customTabsIntent,
true,
false,
requireActivity()
)
}
companion object {
private const val ARG_POSITION = "position"
private const val ARG_ITEMS = "items"
fun newInstance(
item: Item
position: Int,
allItems: ArrayList<Item>
): ArticleFragment {
val fragment = ArticleFragment()
val args = Bundle()
args.putParcelable(ARG_ITEMS, item)
args.putInt(ARG_POSITION, position)
args.putParcelableArrayList(ARG_ITEMS, allItems)
fragment.arguments = args
return fragment
}

View File

@ -0,0 +1,142 @@
package apps.amine.bou.readerforselfoss.settings;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.appbar.AppBarLayout;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import com.ftinc.scoop.Scoop;
import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.themes.AppColors;
import apps.amine.bou.readerforselfoss.themes.Toppings;
/**
* A {@link PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
new AppColors(this);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
AppBarLayout bar = (AppBarLayout) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
Toolbar toolbar = bar.findViewById(R.id.toolbar);
Scoop scoop = Scoop.getInstance();
scoop.bind(this, Toppings.PRIMARY.getValue(), toolbar);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.getValue());
}
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
root.addView(bar, 0);
getDelegate().onPostCreate(savedInstanceState);
}
ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@NonNull
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@ -0,0 +1,316 @@
package apps.amine.bou.readerforselfoss.settings;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import androidx.appcompat.app.ActionBar;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import java.util.List;
import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.themes.AppColors;
import apps.amine.bou.readerforselfoss.utils.Config;
/**
* A {@link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class SettingsActivity extends AppCompatPreferenceActivity {
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private static final Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, value) -> {
String stringValue = value.toString();
preference.setSummary(stringValue);
return true;
};
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
private static void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
@Override
protected void onCreate(Bundle savedInstanceState) {
new AppColors(this);
super.onCreate(savedInstanceState);
setupActionBar();
}
/**
* Set up the {@link android.app.ActionBar}, if the API is available.
*/
private void setupActionBar() {
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
// Show the Up button in the action bar.
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this);
}
/**
* {@inheritDoc}
*/
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.pref_headers, target);
AppColors appColors = new AppColors(this);
if (appColors.isDarkTheme()) {
for (Header header : target) {
tryLoadIconDark(header);
}
}
}
private void tryLoadIconDark(Header header){
try{
if (header.fragmentArguments != null) {
String iconDark = header.fragmentArguments.getString("iconDark");
int iconDarkId = getResources().getIdentifier(iconDark, "drawable", getPackageName());
if (iconDarkId != 0) {
header.iconRes = iconDarkId;
}
}
} catch (Exception e) {
Log.e("SettingsActivity", "Can not load dark icon", e);
}
}
/**
* This method stops fragment injection in malicious applications.
* Make sure to deny any unknown fragments here.
*/
@Override
protected boolean isValidFragment(String fragmentName) {
return PreferenceFragment.class.getName().equals(fragmentName)
|| GeneralPreferenceFragment.class.getName().equals(fragmentName)
|| ArticleViewerPreferenceFragment.class.getName().equals(fragmentName)
|| OfflinePreferenceFragment.class.getName().equals(fragmentName)
|| ExperimentalPreferenceFragment.class.getName().equals(fragmentName)
|| LinksPreferenceFragment.class.getName().equals(fragmentName)
|| ThemePreferenceFragment.class.getName().equals(fragmentName);
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class GeneralPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
setHasOptionsMenu(true);
EditTextPreference itemsNumber = (EditTextPreference) findPreference("prefer_api_items_number");
itemsNumber.getEditText().setFilters(new InputFilter[]{
(source, start, end, dest, dstart, dend) -> {
try {
int input = Integer.parseInt(dest.toString() + source.toString());
if (input <= 200 && input > 0)
return null;
} catch (NumberFormatException nfe) {
Toast.makeText(getActivity(), R.string.items_number_should_be_number, Toast.LENGTH_LONG).show();
}
return "";
}
});
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ArticleViewerPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_viewer);
setHasOptionsMenu(true);
final EditTextPreference fontSize = (EditTextPreference) findPreference("reader_font_size");
fontSize.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) throws NumberFormatException {
fontSize.getEditText().setTextSize(Integer.parseInt(editable.toString()));
}
});
fontSize.getEditText().setFilters(new InputFilter[]{
(source, start, end, dest, dstart, dend) -> {
try {
int input = Integer.parseInt(dest.toString() + source.toString());
if (input > 0)
return null;
} catch (NumberFormatException ignored) {}
return "";
}
});
}
}
/**
* This fragment shows general preferences only. It is used when the
* activity is showing a two-pane settings UI.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class LinksPreferenceFragment extends PreferenceFragment {
public void openUrl(Uri uri) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(browserIntent);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_links);
setHasOptionsMenu(true);
findPreference("trackerLink").setOnPreferenceClickListener(preference -> {
openUrl(Uri.parse(Config.trackerUrl));
return true;
});
findPreference("sourceLink").setOnPreferenceClickListener(preference -> {
openUrl(Uri.parse(Config.sourceUrl));
return false;
});
findPreference("translation").setOnPreferenceClickListener(preference -> {
openUrl(Uri.parse(Config.translationUrl));
return false;
});
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ThemePreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_theme);
setHasOptionsMenu(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.clear) {
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getActivity());
SharedPreferences.Editor editor = pref.edit();
editor.remove("color_primary");
editor.remove("color_primary_dark");
editor.remove("color_accent");
editor.remove("color_accent_dark");
editor.remove("dark_theme");
editor.apply();
getActivity().recreate();
}
return super.onOptionsItemSelected(item);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.settings_theme, menu);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class OfflinePreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_offline);
setHasOptionsMenu(true);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class ExperimentalPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_experimental);
setHasOptionsMenu(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
super.onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -1,221 +0,0 @@
package apps.amine.bou.readerforselfoss.settings
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.*
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceManager
import android.view.*
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.databinding.ActivitySettingsBinding
import apps.amine.bou.readerforselfoss.themes.Toppings
import apps.amine.bou.readerforselfoss.utils.Config
import com.ftinc.scoop.Scoop
import java.lang.NumberFormatException
private const val TITLE_TAG = "settingsActivityTitle"
class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false)) {
setTheme(R.style.NoBarDark)
}
val binding = ActivitySettingsBinding.inflate(layoutInflater)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
setContentView(binding.root)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, MainPreferenceFragment())
.commit()
} else {
title = savedInstanceState.getCharSequence(TITLE_TAG)
}
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle(R.string.title_activity_settings)
}
}
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.title = title
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// Save current activity title so we can set it again after a configuration change
outState.putCharSequence(TITLE_TAG, title)
}
override fun onSupportNavigateUp(): Boolean {
if (supportFragmentManager.popBackStackImmediate()) {
supportActionBar?.title = getText(R.string.title_activity_settings)
return true
}
return super.onSupportNavigateUp()
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Instantiate the new Fragment
val args = pref.extras
val fragment = supportFragmentManager.fragmentFactory.instantiate(
classLoader,
pref.fragment
).apply {
arguments = args
setTargetFragment(caller, 0)
}
// Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment)
.addToBackStack(null)
.commit()
title = pref.title
supportActionBar?.title = title
return true
}
class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_main, rootKey)
}
}
class GeneralPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_general, rootKey)
val editTextPreference = preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number")
editTextPreference?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.filters = arrayOf(
InputFilter { source, _, _, dest, _, _ ->
try {
val input: Int = (dest.toString() + source.toString()).toInt()
if (input in 1..200) return@InputFilter null
} catch (nfe: NumberFormatException) {
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
}
""
}
)
}
}
}
class ArticleViewerPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_viewer, rootKey)
val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size")
fontSize?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.addTextChangedListener { object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
try {
editText.textSize = editable.toString().toInt().toFloat()
} catch (e: NumberFormatException) {
}
}
} }
editText.filters = arrayOf(
InputFilter { source, _, _, dest, _, _ ->
try {
val input = (dest.toString() + source.toString()).toInt()
if (input > 0) return@InputFilter null
} catch (nfe: NumberFormatException) {
}
""
}
)
}
}
}
class OfflinePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_offline, rootKey)
}
}
class ThemePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_theme, rootKey)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.settings_theme, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.clear) {
val pref = PreferenceManager.getDefaultSharedPreferences(activity)
val editor = pref.edit()
editor.remove("color_primary")
editor.remove("color_primary_dark")
editor.remove("color_accent")
editor.remove("color_accent_dark")
editor.remove("dark_theme")
editor.apply()
requireActivity().recreate()
}
return super.onOptionsItemSelected(item)
}
}
class LinksPreferenceFragment : PreferenceFragmentCompat() {
private fun openUrl(uri: Uri?) {
val browserIntent = Intent(Intent.ACTION_VIEW, uri)
startActivity(browserIntent)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_links, rootKey)
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openUrl(Uri.parse(Config.trackerUrl))
true
}
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openUrl(Uri.parse(Config.sourceUrl))
false
}
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openUrl(Uri.parse(Config.translationUrl))
false
}
}
}
class ExperimentalPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_experimental, rootKey)
}
}
}

View File

@ -1,8 +1,11 @@
package apps.amine.bou.readerforselfoss.themes
import android.app.Activity
import android.content.Context
import android.os.Build
import android.preference.PreferenceManager
import androidx.annotation.ColorInt
import androidx.preference.PreferenceManager
import android.util.TypedValue
import apps.amine.bou.readerforselfoss.R
class AppColors(a: Activity) {
@ -11,6 +14,7 @@ class AppColors(a: Activity) {
@ColorInt val colorPrimaryDark: Int
@ColorInt val colorAccent: Int
@ColorInt val colorAccentDark: Int
@ColorInt val cardBackgroundColor: Int
@ColorInt val colorBackground: Int
@ColorInt val textColor: Int
val isDarkTheme: Boolean
@ -18,6 +22,28 @@ class AppColors(a: Activity) {
init {
val sharedPref = PreferenceManager.getDefaultSharedPreferences(a)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
colorPrimary =
sharedPref.getInt(
"color_primary",
a.resources.getColor(R.color.colorPrimary, a.theme)
)
colorPrimaryDark =
sharedPref.getInt(
"color_primary_dark",
a.resources.getColor(R.color.colorPrimaryDark, a.theme)
)
colorAccent =
sharedPref.getInt(
"color_accent",
a.resources.getColor(R.color.colorAccent, a.theme)
)
colorAccentDark =
sharedPref.getInt(
"color_accent_dark",
a.resources.getColor(R.color.colorAccentDark, a.theme)
)
} else {
colorPrimary =
sharedPref.getInt(
"color_primary",
@ -38,6 +64,7 @@ class AppColors(a: Activity) {
"color_accent_dark",
a.resources.getColor(R.color.colorAccentDark)
)
}
isDarkTheme =
sharedPref.getBoolean(
"dark_theme",
@ -49,7 +76,7 @@ class AppColors(a: Activity) {
R.color.darkBackground
} else {
a.setTheme(R.style.NoBar)
R.color.grey_50
android.R.color.background_light
}
textColor = if (isDarkTheme) {
@ -57,5 +84,14 @@ class AppColors(a: Activity) {
} else {
R.color.grey_900
}
val wrapper = Context::class.java
val method = wrapper.getMethod("getThemeResId")
method.isAccessible = true
val typedCardBackground = TypedValue()
a.theme.resolveAttribute(R.attr.cardBackgroundColor, typedCardBackground, true)
cardBackgroundColor = typedCardBackground.data
}
}

View File

@ -0,0 +1,43 @@
package apps.amine.bou.readerforselfoss.transformers
import androidx.viewpager.widget.ViewPager
import android.view.View
class DepthPageTransformer : ViewPager.PageTransformer {
override fun transformPage(view: View, position: Float) {
val pageWidth = view.width
when {
position < -1 -> // [-Infinity,-1)
// This page is way off-screen to the left.
view.alpha = 0F
position <= 0 -> { // [-1,0]
// Use the default slide transition when moving to the left page
view.alpha = 1F
view.translationX = 0F
view.scaleX = 1F
view.scaleY = 1F
}
position <= 1 -> { // (0,1]
// Fade the page out.
view.alpha = 1 - position
// Counteract the default slide transition
view.translationX = pageWidth * -position
// Scale the page down (between MIN_SCALE and 1)
val scaleFactor = MIN_SCALE + (1 - MIN_SCALE) * (1 - Math.abs(position))
view.scaleX = scaleFactor
view.scaleY = scaleFactor
}
else -> // (1,+Infinity]
// This page is way off-screen to the right.
view.alpha = 0F
}
}
companion object {
private val MIN_SCALE = 0.75f
}
}

View File

@ -4,12 +4,11 @@ import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import apps.amine.bou.readerforselfoss.LoginActivity
class Config(c: Context) {
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
val settings: SharedPreferences = c.getSharedPreferences(settingsName, Context.MODE_PRIVATE)
val baseUrl: String
get() = settings.getString("url", "")!!
@ -41,17 +40,18 @@ class Config(c: Context) {
const val newItemsChannelId = "new-items-channel-id"
var apiVersion = 0
var dateTimeFormatter = "yyyy-MM-dd HH:mm:ss"
/* Execute logout and clear all settings to default */
fun logoutAndRedirect(
c: Context,
callingActivity: Activity,
editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false
): Boolean {
val settings = PreferenceManager.getDefaultSharedPreferences(c)
settings.edit().clear().commit()
editor.remove("url")
editor.remove("login")
editor.remove("password")
editor.apply()
val intent = Intent(c, LoginActivity::class.java)
if (baseUrlFail) {
intent.putExtra("baseUrlFail", baseUrlFail)

View File

@ -1,31 +0,0 @@
package apps.amine.bou.readerforselfoss.utils
import android.text.format.DateUtils
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
fun parseDate(dateString: String): Instant {
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
return if (Config.apiVersion >= 4) {
OffsetDateTime.parse(dateString).toInstant()
} else {
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)
}
}
fun parseRelativeDate(dateString: String): String {
val date = parseDate(dateString)
return " " + DateUtils.getRelativeTimeSpanString(
date.toEpochMilli(),
Instant.now().toEpochMilli(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
}

View File

@ -1,12 +1,16 @@
package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.text.format.DateUtils
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossTagType
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
fun String.toTextDrawableString(c: Context): String {
val textDrawable = StringBuilder()
for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) {
for (s in this.split(" ".toRegex()).filter { !it.isEmpty() }.toTypedArray()) {
try {
textDrawable.append(s[0])
} catch (e: StringIndexOutOfBoundsException) {
@ -16,7 +20,17 @@ fun String.toTextDrawableString(c: Context): String {
}
fun Item.sourceAndDateText(): String {
val formattedDate = parseRelativeDate(this.datetime)
val formattedDate: String = try {
" " + DateUtils.getRelativeTimeSpanString(
SimpleDateFormat(Config.dateTimeFormatter).parse(this.datetime).time,
Date().time,
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
} catch (e: ParseException) {
e.printStackTrace()
""
}
return this.getSourceTitle() + formattedDate
}

View File

@ -86,18 +86,6 @@ fun Context.openItemUrlInternally(
intent.putExtra("currentItem", currentItem)
app.startActivity(intent)
} else {
this.openItemUrlInternalBrowser(
linkDecoded,
customTabsIntent,
app)
}
}
fun Context.openItemUrlInternalBrowser(
linkDecoded: String,
customTabsIntent: CustomTabsIntent,
app: Activity
) {
try {
CustomTabActivityHelper.openCustomTab(
app,
@ -112,6 +100,7 @@ fun Context.openItemUrlInternalBrowser(
openInBrowser(linkDecoded, app)
}
}
}
fun Context.openItemUrl(
allItems: ArrayList<Item>,
@ -132,7 +121,7 @@ fun Context.openItemUrl(
} else {
if (!internalBrowser) {
openInBrowser(linkDecoded, app)
} else if (articleViewer) {
} else {
this.openItemUrlInternally(
allItems,
currentItem,
@ -141,12 +130,6 @@ fun Context.openItemUrl(
articleViewer,
app
)
} else {
this.openItemUrlInternalBrowser(
linkDecoded,
customTabsIntent,
app
)
}
}
}

View File

@ -398,7 +398,7 @@ object SharedItems {
}
private fun sortItems() {
val tmpItems = ArrayList(items.sortedByDescending { parseDate(it.datetime) })
val tmpItems = ArrayList(items.sortedByDescending { SimpleDateFormat(Config.dateTimeFormatter).parse((it.datetime)) })
items = tmpItems
}
}

View File

@ -13,7 +13,7 @@ import java.lang.ref.WeakReference;
*/
public class ServiceConnection extends CustomTabsServiceConnection {
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
private final WeakReference<ServiceConnectionCallback> mConnectionCallback;
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
mConnectionCallback = new WeakReference<>(connectionCallback);

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4 -1.5,0 -2.89,0.19 -4.15,0.48L18.18,13.8 23.64,7zM17.04,15.22L3.27,1.44 2,2.72l2.05,2.06C1.91,5.76 0.59,6.82 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01 3.9,-4.86 3.32,3.32 1.27,-1.27 -3.46,-3.46z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,13v8h8v-8h-8zM3,21h8v-8L3,13v8zM3,3v8h8L11,3L3,3zM16.66,1.69L11,7.34 16.66,13l5.66,-5.66 -5.66,-5.65z"/>
</vector>

View File

@ -7,7 +7,7 @@
tools:context="apps.amine.bou.readerforselfoss.AddSourceActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
@ -55,8 +55,7 @@
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:gravity="center_horizontal" />
android:layout_marginLeft="16dp"/>
<EditText
android:layout_width="match_parent"
@ -68,9 +67,7 @@
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:inputType="text"
android:hint="@string/add_source_hint_name"
android:textColorHint="?android:textColorPrimary"
android:autofillHints="false" />
android:hint="@string/add_source_hint_name"/>
<EditText
android:layout_width="match_parent"
@ -79,12 +76,10 @@
android:ems="10"
android:id="@+id/sourceUri"
android:hint="@string/add_source_hint_url"
android:textColorHint="?android:textColorPrimary"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/nameInput"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:autofillHints="false" />
app:layout_constraintLeft_toLeftOf="parent"/>
<EditText
android:layout_width="match_parent"
@ -96,9 +91,7 @@
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/sourceUri"
android:hint="@string/add_source_hint_tags"
android:textColorHint="?android:textColorPrimary"
android:inputType="text"
android:autofillHints="false" />
android:inputType="text"/>
<Spinner
android:layout_width="match_parent"
@ -107,8 +100,7 @@
app:layout_constraintTop_toBottomOf="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_height="40dp"
android:theme="@style/App.Spinner"/>
android:layout_height="40dp"/>
<Button
android:text="@string/add_source_save"

View File

@ -21,7 +21,7 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"

View File

@ -45,69 +45,98 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/urlLayout"
>
<EditText
android:id="@+id/urlView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/prompt_url"
android:imeOptions="actionUnspecified"
android:importantForAutofill="no"
android:inputType="textUri"
android:maxLines="1" />
<com.google.android.material.switchmaterial.SwitchMaterial
</com.google.android.material.textfield.TextInputLayout>
<Switch
android:text="@string/withLoginSwitch"
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/withLogin"
android:layout_weight="1"/>
<EditText
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/loginLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<AutoCompleteTextView
android:id="@+id/loginView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/prompt_login"
android:inputType="text"
android:maxLines="1"
android:visibility="gone" />
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/passwordLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<EditText
android:id="@+id/passwordView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/prompt_password"
android:inputType="textPassword"
android:maxLines="1"
android:visibility="gone" />
android:maxLines="1" />
<com.google.android.material.switchmaterial.SwitchMaterial
</com.google.android.material.textfield.TextInputLayout>
<Switch
android:id="@+id/withHttpLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/withHttpLoginSwitch" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/httpLoginInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<EditText
android:inputType="text"
android:id="@+id/httpLoginView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:hint="@string/prompt_http_login"
android:inputType="text"
android:visibility="gone" />
android:hint="@string/prompt_http_login" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/httpPasswordInput"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<EditText
android:id="@+id/httpPasswordView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:hint="@string/prompt_http_password"
android:inputType="textPassword"
android:visibility="gone" />
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
<Switch
android:id="@+id/withSelfhostedCert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -126,8 +155,8 @@
style="?android:textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold" />

View File

@ -22,7 +22,7 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
@ -33,7 +33,7 @@
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
<me.relex.circleindicator.CircleIndicator3
<me.relex.circleindicator.CircleIndicator
android:id="@+id/indicator"
android:layout_width="match_parent"
android:layout_height="20dp"

View File

@ -1,22 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -36,8 +36,9 @@
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/add_source" />
android:layout_marginRight="16dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -16,8 +16,7 @@
app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true"
card_view:layout_constraintBottom_toBottomOf="parent"
app:cardBackgroundColor="?cardBackgroundColor">
card_view:layout_constraintBottom_toBottomOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@ -66,7 +65,6 @@
android:gravity="start"
android:textAlignment="viewStart"
android:textStyle="bold"
android:textColor="?android:textColorPrimary"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/sourceImage"
app:layout_constraintRight_toRightOf="parent"
@ -81,7 +79,6 @@
android:gravity="start"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textColor="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Google Actualité Il y a 5h" />

View File

@ -4,7 +4,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:colorBackground"
android:descendantFocusability="blocksDescendants">
<androidx.core.widget.NestedScrollView
@ -34,7 +33,7 @@
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:textColor="?android:textColorSecondary"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="12sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
@ -66,7 +65,6 @@
android:layout_marginRight="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
android:background="?android:colorBackground"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
@ -96,13 +94,13 @@
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom|right"
android:layout_gravity="end|bottom|end"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add_white_24dp"
app:srcCompat="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorAccent"
app:fabSize="mini"
app:rippleColor="?attr/colorAccentDark" />

View File

@ -12,8 +12,7 @@
android:layout_marginStart="8dp"
android:layout_marginTop="21dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="8dp" />
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/title"
@ -30,14 +29,11 @@
android:textAllCaps="false"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="Titre"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp" />
tools:text="Titre" />
<TextView
android:id="@+id/sourceTitleAndDate"
@ -50,13 +46,10 @@
android:maxLines="1"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
tools:text="Google Actualité Il y a 5h"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp" />
tools:text="Google Actualité Il y a 5h" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.appbar.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
</com.google.android.material.appbar.AppBarLayout>

View File

@ -1,50 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="vertical">
android:layout_height="48dp">
<ImageView
android:id="@+id/itemImage"
android:layout_width="36dp"
android:layout_height="36dp"
android:importantForAccessibility="no"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_marginLeft="16dp"
android:layout_width="36dp"
android:layout_height="36dp"/>
<TextView
android:id="@+id/sourceTitle"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="17dp"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:gravity="start"
android:maxLines="1"
android:textAlignment="textStart"
android:textSize="13sp"
android:textColor="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
app:layout_constraintStart_toEndOf="@+id/itemImage"
android:id="@+id/sourceTitle"
app:layout_constraintTop_toTopOf="parent"
tools:text="source title" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/itemImage"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:textSize="13sp"
android:textAlignment="viewStart"
tools:text="source title "
android:gravity="start" />
<Button
android:id="@+id/deleteBtn"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="34dp"
android:layout_height="34dp"
android:layout_marginEnd="8dp"
android:background="@drawable/ic_remove_circle_outline_black_24dp"
style="@style/Widget.AppCompat.Button.Borderless"
android:backgroundTint="?android:textColorSecondary"
android:elevation="4dp"
android:contentDescription="@string/remove_source"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"
android:layout_marginStart="8dp"
app:layout_constraintLeft_toRightOf="@+id/sourceTitle"
android:layout_marginLeft="8dp"
app:layout_constraintHorizontal_bias="1.0"
android:layout_width="34dp"
android:layout_height="34dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Barre statique pour le visionneur d\'articles</string>
<string name="reader_static_bar_on">La barre sera affichée</string>
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">"Lector para selfoss"</string>
<string name="title_activity_login">"Conectar"</string>
<string name="app_name">"Lector para Selfoss"</string>
<string name="title_activity_login">"Acceder"</string>
<string name="prompt_password">"Contrasinal"</string>
<string name="prompt_http_password">"Contrasinal HTTP"</string>
<string name="action_sign_in">"Ir"</string>
@ -9,7 +9,7 @@
<string name="error_field_required">"Campo requirido"</string>
<string name="prompt_url">"URL"</string>
<string name="withLoginSwitch">"É preciso iniciar sesión?"</string>
<string name="withHttpLoginSwitch">"É preciso iniciar sesión?"</string>
<string name="withHttpLoginSwitch">"É preciso iniciar sesión HTTP?"</string>
<string name="login_url_problem">"Ups! Pode que precises engadir un \"/\" o final da URL."</string>
<string name="prompt_login">"Nome de usuario"</string>
<string name="prompt_http_login">"Nome de usuario HTTP"</string>
@ -139,8 +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_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_update_sources">Comproba novas fontes e etiquetas</string>
<string name="pref_switch_update_sources_summary">Deshabilita isto se o teu servidor está recibindo demasiadas peticións de base de datos.</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="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>
@ -162,8 +162,7 @@
<string name="reader_text_align_left">Aliñar á esquerda</string>
<string name="reader_text_align_justify">Xustificado</string>
<string name="settings_reader_font">Modo lector</string>
<string name="reader_static_bar_title">Barra inferior estática na vista de artigos</string>
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -162,8 +162,7 @@
<string name="reader_text_align_left">左对齐</string>
<string name="reader_text_align_justify">左右对齐</string>
<string name="settings_reader_font">阅读器字体</string>
<string name="reader_static_bar_title">文章查看器中的静态底部栏</string>
<string name="reader_static_bar_on">底部栏将始终显示</string>
<string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string>
<string name="remove_source">删除源</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
</resources>

View File

@ -165,5 +165,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -5,7 +5,7 @@
<color name="colorAccent">#FFff5722</color>
<color name="colorAccentDark">#FFbf360c</color>
<color name="pink">#FFe91e63</color>
<color name="white">#FFFFFF</color>
<color name="white">#FFFFFFFF</color>
<color name="black">#FF000000</color>
<color name="red">#FF0000</color>
<color name="refresh_progress_1">@color/colorAccentDark</color>
@ -20,5 +20,5 @@
<color name="cardBackgroundColor">#FFFFFFFF</color>
<color name="materialDrawerHeaderSelectionText">#212121</color>
<color name="darkBackground">#303030</color>
<color name="darkBackground">#FF303030</color>
</resources>

View File

@ -168,5 +168,4 @@
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
</resources>

View File

@ -14,10 +14,9 @@
<item name="colorSurface">@color/grey_50</item>
<item name="android:textColorPrimary">@color/grey_900</item>
<item name="android:textColorSecondary">@color/grey_400</item>
<item name="materialDrawerStyle">@style/App.materialDrawerStyle</item>
<item name="materialDrawerStyle">@style/Widget.MaterialDrawerStyle</item>
<item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="preferenceTheme">@style/PreferenceStyle</item>
</style>
<style name="NoBarDark" parent="Theme.MaterialComponents.DayNight.NoActionBar">
@ -28,14 +27,12 @@
<item name="cardBackgroundColor">@color/grey_800</item>
<item name="android:colorBackground">@color/darkBackground</item>
<item name="colorSurface">@color/darkBackground</item>
<item name="alertDialogTheme">@style/AlertDialogDark</item>
<item name="bnbBackgroundColor">@color/grey_900</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/grey_600</item>
<item name="materialDrawerStyle">@style/App.materialDrawerStyle</item>
<item name="materialDrawerStyle">@style/Widget.MaterialDrawerStyle</item>
<item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="preferenceTheme">@style/PreferenceStyle</item>
</style>
<!-- ToolBar -->
@ -47,27 +44,4 @@
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>-->
</style>
<!-- Material Drawer Theme -->
<style name="App.materialDrawerStyle" parent="@style/Widget.MaterialDrawerStyle">
<item name="materialDrawerPrimaryIcon">?android:textColorPrimary</item>
<item name="materialDrawerSecondaryIcon">?android:textColorPrimary</item>
<item name="materialDrawerSecondaryText">?android:textColorPrimary</item>
</style>
<!-- Preference Theme -->
<style name="PreferenceStyle" parent="@style/PreferenceThemeOverlay">
<item name="android:tint">?android:textColorPrimary</item>
</style>
<!-- Spinner Theme -->
<style name="App.Spinner" parent="Widget.AppCompat.Light.DropDownItem.Spinner">
<item name="android:textColor">?android:textColorPrimary</item>
</style>
<!-- Alert dialog Theme -->
<style name="AlertDialogDark" parent="Theme.MaterialComponents.Dialog">
<item name="android:background">@color/darkBackground</item>
</style>
</resources>

View File

@ -1,11 +1,9 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:inputType="number"
android:key="api_timeout"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_api_timeout"
app:iconSpaceReserved="false"/>
android:title="@string/pref_api_timeout" />
</PreferenceScreen>

View File

@ -1,5 +1,4 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/pref_selfoss_category">
@ -11,8 +10,7 @@
android:key="prefer_api_items_number"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_api_items_number_title"
app:iconSpaceReserved="false"/>
android:title="@string/pref_api_items_number_title" />
<EditTextPreference
android:defaultValue=""
@ -20,14 +18,12 @@
android:key="hidden_tags"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_hidden_tags"
app:iconSpaceReserved="false"/>
android:title="@string/pref_hidden_tags" />
<SwitchPreference
android:defaultValue="false"
android:key="infinite_loading"
android:title="@string/pref_general_infinite_loading_title"
app:iconSpaceReserved="false"/>
android:title="@string/pref_general_infinite_loading_title" />
<PreferenceCategory
android:title="@string/pref_general_category_links">
@ -37,24 +33,21 @@
android:key="prefer_internal_browser"
android:summaryOff="@string/pref_general_internal_browser_off"
android:summaryOn="@string/pref_general_internal_browser_on"
android:title="@string/pref_general_internal_browser_title"
app:iconSpaceReserved="false"/>
android:title="@string/pref_general_internal_browser_title" />
<SwitchPreference
android:defaultValue="true"
android:dependency="prefer_internal_browser"
android:key="prefer_article_viewer"
android:summaryOff="@string/prefer_article_viewer_off"
android:summaryOn="@string/prefer_article_viewer_on"
android:title="@string/prefer_article_viewer_title"
app:iconSpaceReserved="false"/>
android:title="@string/prefer_article_viewer_title" />
<SwitchPreference
android:defaultValue="false"
android:dependency="prefer_article_viewer"
android:key="reader_static_bar"
android:summaryOff="@string/reader_static_bar_off"
android:summaryOn="@string/reader_static_bar_on"
android:title="@string/reader_static_bar_title"
app:iconSpaceReserved="false"/>
android:title="@string/reader_static_bar_title" />
<PreferenceCategory
android:title="@string/pref_general_category_displaying">
@ -64,34 +57,29 @@
android:defaultValue="false"
android:key="account_header_displaying"
android:summary="@string/display_header_drawer_summary"
android:title="@string/display_header_drawer_title"
app:iconSpaceReserved="false"/>
android:title="@string/display_header_drawer_title" />
<SwitchPreference
android:defaultValue="false"
android:key="card_view_active"
android:summaryOff="@string/pref_switch_card_view_off"
android:summaryOn="@string/pref_switch_card_view_on"
android:title="@string/pref_switch_card_view_title"
app:iconSpaceReserved="false"/>
android:title="@string/pref_switch_card_view_title" />
<SwitchPreference
android:defaultValue="false"
android:dependency="card_view_active"
android:key="full_height_cards"
android:summaryOff="@string/card_height_off"
android:summaryOn="@string/card_height_on"
android:title="@string/card_height_title"
app:iconSpaceReserved="false"/>
android:title="@string/card_height_title" />
<SwitchPreference
android:defaultValue="true"
android:key="display_unread_count"
android:summaryOn="@string/switch_unread_count"
android:title="@string/switch_unread_count_title"
app:iconSpaceReserved="false"/>
android:title="@string/switch_unread_count_title" />
<SwitchPreference
android:defaultValue="false"
android:dependency="display_unread_count"
android:key="display_other_count"
android:title="@string/display_all_counts_title"
app:iconSpaceReserved="false"/>
android:title="@string/display_all_counts_title" />
</PreferenceScreen>

View File

@ -0,0 +1,57 @@
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$GeneralPreferenceFragment"
android:icon="@drawable/ic_settings_black_24dp"
android:title="@string/pref_header_general">
<extra
android:name="iconDark"
android:value="ic_settings_white_24dp"/>
</header>
<header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$ArticleViewerPreferenceFragment"
android:icon="@drawable/ic_chrome_reader_mode_black_24dp"
android:title="@string/pref_header_viewer">
<extra
android:name="iconDark"
android:value="ic_chrome_reader_mode_white_24dp"/>
</header>
<header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$OfflinePreferenceFragment"
android:icon="@drawable/ic_signal_wifi_off_black_24dp"
android:title="@string/pref_header_offline">
<extra
android:name="iconDark"
android:value="ic_signal_wifi_off_white_24dp"/>
</header>
<header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$ThemePreferenceFragment"
android:icon="@drawable/ic_color_lens_black_24dp"
android:title="@string/pref_header_theme">
<extra
android:name="iconDark"
android:value="ic_color_lens_white_24dp"/>
</header>
<header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$LinksPreferenceFragment"
android:icon="@drawable/ic_info_black_24dp"
android:title="@string/pref_header_links">
<extra
android:name="iconDark"
android:value="ic_info_white_24dp"/>
</header>
<header
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$ExperimentalPreferenceFragment"
android:icon="@drawable/ic_widgets_black_24dp"
android:title="@string/pref_header_experimental">
<extra
android:name="iconDark"
android:value="ic_widgets_white_24dp"/>
</header>
</preference-headers>

View File

@ -1,17 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference android:title="@string/issue_tracker_link"
android:summary="@string/issue_tracker_summary"
android:key="trackerLink"
app:iconSpaceReserved="false"/>
android:key="trackerLink" />
<Preference android:title="@string/source_code"
android:key="sourceLink"
app:iconSpaceReserved="false"/>
android:key="sourceLink" />
<Preference android:title="@string/translation"
android:key="translation"
app:iconSpaceReserved="false"/>
android:key="translation" />
</PreferenceScreen>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/title_activity_settings">
<Preference
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$GeneralPreferenceFragment"
android:title="@string/pref_header_general"
android:icon="@drawable/ic_settings_black_24dp" />
<Preference
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$ArticleViewerPreferenceFragment"
android:title="@string/pref_header_viewer"
android:icon="@drawable/ic_chrome_reader_mode_black_24dp" />
<Preference
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$OfflinePreferenceFragment"
android:title="@string/pref_header_offline"
android:icon="@drawable/ic_signal_wifi_off_black_24dp" />
<Preference
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$ThemePreferenceFragment"
android:title="@string/pref_header_theme"
android:icon="@drawable/ic_color_lens_black_24dp" />
<Preference
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$LinksPreferenceFragment"
android:title="@string/pref_header_links"
android:icon="@drawable/ic_info_black_24dp" />
<Preference
android:fragment="apps.amine.bou.readerforselfoss.settings.SettingsActivity$ExperimentalPreferenceFragment"
android:title="@string/pref_header_experimental"
android:icon="@drawable/ic_widgets_black_24dp" />
</PreferenceScreen>

View File

@ -1,9 +1,7 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:key="items_caching"
app:iconSpaceReserved="false"
android:summaryOff="@string/pref_switch_items_caching_off"
android:summaryOn="@string/pref_switch_items_caching_on"
android:title="@string/pref_switch_items_caching" />
@ -12,7 +10,6 @@
android:defaultValue="false"
android:key="periodic_refresh"
android:dependency="items_caching"
app:iconSpaceReserved="false"
android:summaryOff="@string/pref_switch_periodic_refresh_off"
android:summaryOn="@string/pref_switch_periodic_refresh_on"
android:title="@string/pref_switch_periodic_refresh" />
@ -21,7 +18,6 @@
android:dependency="periodic_refresh"
android:defaultValue="360"
android:inputType="number"
app:iconSpaceReserved="false"
android:key="periodic_refresh_minutes"
android:selectAllOnFocus="true"
android:singleLine="true"
@ -29,21 +25,18 @@
<SwitchPreference
android:defaultValue="false"
app:iconSpaceReserved="false"
android:key="refresh_when_charging"
android:dependency="periodic_refresh"
android:title="@string/pref_switch_refresh_when_charging" />
<SwitchPreference
android:defaultValue="false"
app:iconSpaceReserved="false"
android:key="notify_new_items"
android:dependency="periodic_refresh"
android:title="@string/pref_switch_notify_new_items" />
<SwitchPreference
android:defaultValue="true"
app:iconSpaceReserved="false"
android:key="update_sources"
android:summary="@string/pref_switch_update_sources_summary"
android:title="@string/pref_switch_update_sources" />

View File

@ -1,35 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO translate this file -->
<SwitchPreference
android:defaultValue="false"
android:key="dark_theme"
app:iconSpaceReserved="false"
android:title="Dark theme" />
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
<com.jaredrummler.android.colorpicker.ColorPreference
android:defaultValue="@color/colorPrimary"
android:key="color_primary"
app:iconSpaceReserved="false"
android:title="Primary color"/>
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
<com.jaredrummler.android.colorpicker.ColorPreference
android:defaultValue="@color/colorPrimaryDark"
android:key="color_primary_dark"
app:iconSpaceReserved="false"
android:title="Primary dark color"/>
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
<com.jaredrummler.android.colorpicker.ColorPreference
android:defaultValue="@color/colorAccent"
android:key="color_accent"
app:iconSpaceReserved="false"
android:title="Accent color"/>
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
<com.jaredrummler.android.colorpicker.ColorPreference
android:defaultValue="@color/colorAccentDark"
android:key="color_accent_dark"
app:iconSpaceReserved="false"
android:title="Accent dark color"/>
</PreferenceScreen>

View File

@ -1,9 +1,7 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:key="mark_on_scroll"
app:iconSpaceReserved="false"
android:summaryOff="@string/pref_switch_actions_pager_scroll_off"
android:summaryOn="@string/pref_switch_actions_pager_scroll_on"
android:title="@string/pref_switch_actions_pager_scroll" />
@ -12,7 +10,6 @@
android:defaultValue="16"
android:inputType="number"
android:key="reader_font_size"
app:iconSpaceReserved="false"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_content_reader_font_size" />
@ -21,6 +18,5 @@
android:entries="@array/preloaded_fonts_values"
android:entryValues="@array/preloaded_fonts_keys"
android:key="reader_font"
app:iconSpaceReserved="false"
android:title="@string/settings_reader_font" />
</PreferenceScreen>

View File

@ -2,7 +2,7 @@
buildscript {
ext {
kotlin_version = '1.6.10'
kotlin_version = '1.5.31'
android_version = '1.0.0'
androidx_version = '1.1.0-alpha05'
lifecycle_version = '2.2.0-alpha01'
@ -12,20 +12,24 @@ buildscript {
repositories {
google()
jcenter()
maven { url "https://www.jitpack.io" }
maven {
url "https://jitpack.io"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.android.tools.build:gradle:7.0.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
// For likebutton only
maven { url "https://jitpack.io" }
google()
mavenCentral()
jcenter()
maven { url "https://www.jitpack.io" }
mavenCentral()
}
}