Compare commits

...

13 Commits

40 changed files with 527 additions and 248 deletions

View File

@ -46,12 +46,30 @@ You can directly import this project into IntellIJ/Android Studio.
You'll have to: You'll have to:
- Configure Fabric, or [remove it](https://docs.fabric.io/android/fabric/settings/removing.html#). - Configure fabric and add your `apiKey` and `apiSecret` in the `fabric.properties` file.
- Create a firebase project and add the `google-services.json` to the `app/` folder. - Create a firebase project and add the `google-services.json` to the `app/` folder.
- Define the following in `res/values/strings.xml` or create `res/values/secrets.xml` - Define the following some parameters either in `~/.gradle/gradle.properties` or as gradle parameters (see the examples)
- mercury: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser - mercuryApiKey: A [Mercury](https://mercury.postlight.com/web-parser/) web parser api key for the internal browser
- feedback_email: An email to receive users feedback. - feedbackEmail: An email to receive users feedback.
- source_url: an url to the source code, used in the settings - sourceUrl: an url to the source code, used in the settings
- tracker_url: an url to the tracker, used in the settings - trackerUrl: an url to the tracker, used in the settings
- To run your app, you'll have to add `-P appLoginUrl="your.selfoss-instance.url" -P appLoginUsername="Username" -P appLoginPassword="password"`. (These are only used to run the espresso tests. You should be able to use empty strings or fake values to build the app)
### Examples:
#### Inside ~/.gradle/gradle.properties
```
appLoginUrl="URL"
appLoginUsername="LOGIN"
appLoginPassword="PASS"
mercuryApiKey="LONGAPIKEY"
feedbackEmail="EMAIL"
sourceUrl="URLSOURCE"
trackerUrl="URLTRACKER"
```
#### As gradle parameters
```
./gradlew .... -P appLoginUrl="URL" -P appLoginUsername="LOGIN" -P appLoginPassword="PASS" -P mercuryApiKey="LONGAPIKEY" -P feedbackEmail="EMAIL" -P sourceUrl="URLSOURCE" -P trackerUrl="URLTRACKER"
```

2
.gitignore vendored
View File

@ -214,6 +214,4 @@ gradle-app.setting
# End of https://www.gitignore.io/api/java,gradle,android,androidstudio # End of https://www.gitignore.io/api/java,gradle,android,androidstudio
secrets.xml
release/ release/

View File

@ -1,3 +1,32 @@
**1.5.3.00**
- (BETA) Added pull from bottom to load more pages of results. May be buggy.
**1.5.2.18/19**
- APK minification finally working. That means less space taken !
- Added an option to log every API call.
**1.5.2.17**
- Source code and tracker links weren't being set, and updated the contributing doc.
**1.5.2.15/16**
- Adding an account header on the lateral drawer.
- The account header is only displayed when the setting is enabled.
**1.5.2.13/14**
- Updated glide.
- Loading images from self signed certificate now working.
**1.5.2.12**
- Self signed certificates are now working for loading data. Image are not loading yet.
**1.5.2.11** **1.5.2.11**
- Added a random unique identifier to be used in the logs. - Added a random unique identifier to be used in the logs.

View File

@ -1,17 +1,17 @@
# ReaderForSelfoss # ReaderForSelfoss
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss) [![Gitter chat](https://badges.gitter.im/amine-bou/ReaderForSelfoss.png)](https://gitter.im/amine-bou/ReaderForSelfoss)
[![Build Status](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/badge/icon)](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/) [![Build Status](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/badge/icon)](http://jenkins.amine-bou.fr/job/ReaderForSelfoss/)
[![Gitter chat](https://badges.gitter.im/amine-bou/ReaderForSelfoss.png)](https://gitter.im/amine-bou/ReaderForSelfoss) [![codebeat badge](https://codebeat.co/badges/bce66c0f-fd28-4341-a159-3b6dd22ac854)](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master) [![Code Triagers Badge](https://www.codetriage.com/aminecmi/readerforselfoss/badges/users.svg)](https://www.codetriage.com/aminecmi/readerforselfoss)
[![codebeat badge](https://codebeat.co/badges/bce66c0f-fd28-4341-a159-3b6dd22ac854)](https://codebeat.co/projects/github-com-aminecmi-readerforselfoss-master)
[![Code Triagers Badge](https://www.codetriage.com/aminecmi/readerforselfoss/badges/users.svg)](https://www.codetriage.com/aminecmi/readerforselfoss)
This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en). This is the repo of [Reader For Selfoss](https://play.google.com/store/apps/details?id=apps.amine.bou.readerforselfoss&hl=en).
It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/) It's an RSS Reader for Android, that **only** works with [Selfoss](https://selfoss.aditu.de/)
The last APK built from source is available [here](https://jenkins.amine-bou.fr/job/ReaderForSelfoss/lastSuccessfulBuild/artifact/SignApksBuilder-out/selfoss-key/selfoss/app-githubConfig-release-unsigned.apk/app-githubConfig-release.apk).
## Want to help ? ## Want to help ?

View File

@ -35,7 +35,7 @@ repositories {
android { android {
compileSdkVersion 26 compileSdkVersion 26
buildToolsVersion "26.0.0" buildToolsVersion "26.0.1"
defaultConfig { defaultConfig {
applicationId "apps.amine.bou.readerforselfoss" applicationId "apps.amine.bou.readerforselfoss"
minSdkVersion 16 minSdkVersion 16
@ -53,10 +53,16 @@ android {
// tests // tests
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
buildConfigField "String", "MERCURY_KEY", mercuryApiKey
buildConfigField "String", "FEEDBACK_EMAIL", feedbackEmail
buildConfigField "String", "SOURCE_URL", sourceUrl
buildConfigField "String", "TRACKER_URL", trackerUrl
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro' 'proguard-rules.pro'
} }
@ -83,12 +89,12 @@ android {
dependencies { dependencies {
// Testing // Testing
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.0'
androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:runner:1.0.0'
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource // Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2.2' androidTestCompile 'com.android.support.test.espresso:espresso-contrib:3.0.0'
// Espresso-intents for validation and stubbing of Intents // Espresso-intents for validation and stubbing of Intents
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2' androidTestCompile 'com.android.support.test.espresso:espresso-intents:3.0.0'
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
@ -119,13 +125,13 @@ dependencies {
compile 'agency.tango.android:material-intro-screen:0.0.5' compile 'agency.tango.android:material-intro-screen:0.0.5'
// About // About
compile('com.mikepenz:aboutlibraries:5.9.6@aar') { compile('com.mikepenz:aboutlibraries:5.9.7@aar') {
transitive = true transitive = true
} }
// Retrofit + http logging + okhttp // Retrofit + http logging + okhttp
compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.0' compile 'com.squareup.okhttp3:logging-interceptor:3.9.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.burgstaller:okhttp-digest:1.12' compile 'com.burgstaller:okhttp-digest:1.12'
@ -137,10 +143,11 @@ dependencies {
compile 'org.sufficientlysecure:html-textview:3.3' compile 'org.sufficientlysecure:html-textview:3.3'
// glide // glide
compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.github.bumptech.glide:glide:4.1.1'
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
// Asking politely users to rate the app // Asking politely users to rate the app
compile 'com.github.stkent:amplify:1.5.0' compile 'com.github.stkent:amplify:2.1.0'
// For the article reader // For the article reader
compile 'com.klinkerapps:drag-dismiss-activity:1.4.3' compile 'com.klinkerapps:drag-dismiss-activity:1.4.3'
@ -162,6 +169,7 @@ apply plugin: 'com.google.gms.google-services'
afterEvaluate { afterEvaluate {
initFabricPropertiesIfNeeded() initFabricPropertiesIfNeeded()
initAppLoginPropertiesIfNeeded() initAppLoginPropertiesIfNeeded()
initAppForSecretPropertiesIfNeeded()
} }
def initFabricPropertiesIfNeeded() { def initFabricPropertiesIfNeeded() {
@ -186,3 +194,16 @@ def initAppLoginPropertiesIfNeeded() {
} }
} }
} }
def initAppForSecretPropertiesIfNeeded() {
def propertiesFile = file(System.getProperty("user.home") + '/.gradle/gradle.properties')
if (!propertiesFile.exists()) {
def commentMessage = "This is autogenerated local property from system environment to prevent key to be committed to source control."
ant.propertyfile(file: System.getProperty("user.home") + "/.gradle/gradle.properties", comment: commentMessage) {
entry(key: "mercuryApiKey", value: System.getProperty("mercuryApiKey"))
entry(key: "feedbackEmail", value: System.getProperty("feedbackEmail"))
entry(key: "sourceUrl", value: System.getProperty("sourceUrl"))
entry(key: "trackerUrl", value: System.getProperty("trackerUrl"))
}
}
}

View File

@ -57,3 +57,16 @@
#Bottom bar lib #Bottom bar lib
-dontwarn com.roughike.bottombar.** -dontwarn com.roughike.bottombar.**
# self signed glidemodule
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-dontwarn com.anupcowkur.reservoir.**
-dontwarn javax.annotation.**

View File

@ -64,6 +64,9 @@
<activity android:name=".ReaderActivity" <activity android:name=".ReaderActivity"
android:theme="@style/DragDismissTheme"> android:theme="@style/DragDismissTheme">
</activity> </activity>
<meta-data
android:name="apps.amine.bou.readerforselfoss.utils.glide.SelfSignedGlideModule"
android:value="GlideModule" />
</application> </application>
</manifest> </manifest>

View File

@ -45,7 +45,7 @@ class AddSourceActivity : AppCompatActivity() {
try { try {
val prefs = PreferenceManager.getDefaultSharedPreferences(this) val prefs = PreferenceManager.getDefaultSharedPreferences(this)
api = SelfossApi(this, this@AddSourceActivity, prefs.getBoolean("isSelfSignedCert", false)) api = SelfossApi(this, this@AddSourceActivity, prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("should_log_everything", false))
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
mustLoginToAddSource() mustLoginToAddSource()
} }

View File

@ -64,6 +64,9 @@ import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.mikepenz.materialdrawer.AccountHeader
import com.mikepenz.materialdrawer.AccountHeaderBuilder
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener { class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
@ -93,6 +96,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private var maybeSourceFilter: Sources? = null private var maybeSourceFilter: Sources? = null
private var maybeSearchFilter: String? = null private var maybeSearchFilter: String? = null
private var userIdentifier: String = "" private var userIdentifier: String = ""
private var displayAccountHeader: Boolean = false
private var infiniteScroll: Boolean = false
private lateinit var emptyText: TextView private lateinit var emptyText: TextView
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
@ -110,6 +115,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private lateinit var sharedPref: SharedPreferences private lateinit var sharedPref: SharedPreferences
private lateinit var firebaseRemoteConfig: FirebaseRemoteConfig private lateinit var firebaseRemoteConfig: FirebaseRemoteConfig
private lateinit var appColors: AppColors private lateinit var appColors: AppColors
private var offset: Int = 0
private var firstVisible: Int = 0
private var recyclerViewScrollListener: RecyclerView.OnScrollListener? = null
@ -142,15 +151,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
customTabActivityHelper = CustomTabActivityHelper() customTabActivityHelper = CustomTabActivityHelper()
val dirtyPref = PreferenceManager.getDefaultSharedPreferences(this) val dirtyPref = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
api = SelfossApi(this, this@HomeActivity, dirtyPref.getBoolean("isSelfSignedCert", false)) api = SelfossApi(this, this@HomeActivity, dirtyPref.getBoolean("isSelfSignedCert", false), dirtyPref.getBoolean("should_log_everything", false))
items = ArrayList() items = ArrayList()
appColors = AppColors(this@HomeActivity) appColors = AppColors(this@HomeActivity)
handleBottomBar() handleBottomBar()
handleDrawer() handleDrawer(dirtyPref)
coordinatorLayout = findViewById(R.id.coordLayout) coordinatorLayout = findViewById(R.id.coordLayout)
swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout) swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout)
@ -289,11 +298,31 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
fullHeightCards = sharedPref.getBoolean("full_height_cards", false) fullHeightCards = sharedPref.getBoolean("full_height_cards", false)
itemsNumber = sharedPref.getString("prefer_api_items_number", "200").toInt() itemsNumber = sharedPref.getString("prefer_api_items_number", "200").toInt()
userIdentifier = sharedPref.getString("unique_id", "") userIdentifier = sharedPref.getString("unique_id", "")
displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
} }
private fun handleDrawer() { private fun handleDrawer(dirtyPref: SharedPreferences) {
displayAccountHeader =
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("account_header_displaying", false)
val headerResult: AccountHeader? = if (displayAccountHeader) {
AccountHeaderBuilder()
.withActivity(this)
.withHeaderBackground(R.drawable.bg)
.addProfiles(
ProfileDrawerItem()
.withName(
dirtyPref.getString("url", "")
)
.withIcon(resources.getDrawable(R.mipmap.ic_launcher))
)
.withSelectionListEnabledForSingleProfile(false)
.build()
} else null
drawer = DrawerBuilder() val drawerBuilder =
DrawerBuilder()
.withActivity(this) .withActivity(this)
.withRootView(R.id.drawer_layout) .withRootView(R.id.drawer_layout)
.withToolbar(toolbar) .withToolbar(toolbar)
@ -314,27 +343,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
}) })
.build()
drawer.addStickyFooterItem( if (displayAccountHeader && headerResult != null)
PrimaryDrawerItem() drawerBuilder.withAccountHeader(headerResult)
.withName(R.string.action_about)
.withSelectable(false) drawer = drawerBuilder.build()
.withIcon(R.drawable.ic_info_outline)
.withIconTintingEnabled(true)
.withOnDrawerItemClickListener { _, _, _ ->
LibsBuilder()
.withActivityStyle(
if (appColors.isDarkTheme)
Libs.ActivityStyle.LIGHT_DARK_TOOLBAR
else
Libs.ActivityStyle.DARK
)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(this@HomeActivity)
false
})
drawer.addStickyFooterItem( drawer.addStickyFooterItem(
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(R.string.title_activity_settings) .withName(R.string.title_activity_settings)
@ -366,7 +380,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
else { else {
for (tag in maybeTags) { for (tag in maybeTags) {
val gd: GradientDrawable = GradientDrawable() val gd = GradientDrawable()
gd.setColor(Color.parseColor(tag.color)) gd.setColor(Color.parseColor(tag.color))
gd.shape = GradientDrawable.RECTANGLE gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30) gd.setSize(30, 30)
@ -438,6 +452,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
.withIdentifier(DRAWER_ID_TAGS) .withIdentifier(DRAWER_ID_TAGS)
.withSelectable(false)) .withSelectable(false))
handleTags(maybeDrawerData.tags) handleTags(maybeDrawerData.tags)
drawer.addItem(DividerDrawerItem())
drawer.addItem( drawer.addItem(
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(getString(R.string.drawer_item_sources)) .withName(getString(R.string.drawer_item_sources))
@ -450,6 +465,26 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
} }
) )
handleSources(maybeDrawerData.sources) handleSources(maybeDrawerData.sources)
drawer.addItem(DividerDrawerItem())
drawer.addItem(
PrimaryDrawerItem()
.withName(R.string.action_about)
.withSelectable(false)
.withIcon(R.drawable.ic_info_outline)
.withIconTintingEnabled(true)
.withOnDrawerItemClickListener { _, _, _ ->
LibsBuilder()
.withActivityStyle(
if (appColors.isDarkTheme)
Libs.ActivityStyle.LIGHT_DARK_TOOLBAR
else
Libs.ActivityStyle.DARK
)
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(this@HomeActivity)
false
})
if (!loadedFromCache) if (!loadedFromCache)
@ -538,6 +573,30 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerView.layoutManager = mLayoutManager recyclerView.layoutManager = mLayoutManager
recyclerView.setHasFixedSize(true) recyclerView.setHasFixedSize(true)
if (infiniteScroll) {
if (recyclerViewScrollListener == null)
recyclerViewScrollListener = object: RecyclerView.OnScrollListener() {
override fun onScrolled(localRecycler: RecyclerView?, dx: Int, dy: Int) {
if (dy > 0) {
if (localRecycler != null) {
val lastVisibleItem: Int = when (mLayoutManager) {
is StaggeredGridLayoutManager -> mLayoutManager.findLastCompletelyVisibleItemPositions(null).last()
is GridLayoutManager -> mLayoutManager.findLastCompletelyVisibleItemPosition()
else -> 0
}
if (lastVisibleItem == (items.size - 1)) {
getElementsAccordingToTab(appendResults = true)
}
}
}
}
}
recyclerView.clearOnScrollListeners()
recyclerView.addOnScrollListener(recyclerViewScrollListener)
}
bottomBar.setTabSelectedListener(object: BottomNavigationBar.OnTabSelectedListener { bottomBar.setTabSelectedListener(object: BottomNavigationBar.OnTabSelectedListener {
override fun onTabUnselected(position: Int) = Unit override fun onTabUnselected(position: Int) = Unit
@ -576,26 +635,31 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerView.visibility = View.VISIBLE recyclerView.visibility = View.VISIBLE
} }
private fun getElementsAccordingToTab() = private fun getElementsAccordingToTab(appendResults: Boolean = false) =
when (elementsShown) { when (elementsShown) {
UNREAD_SHOWN -> getUnRead() UNREAD_SHOWN -> getUnRead(appendResults)
READ_SHOWN -> getRead() READ_SHOWN -> getRead(appendResults)
FAV_SHOWN -> getStarred() FAV_SHOWN -> getStarred(appendResults)
else -> getUnRead() else -> getUnRead(appendResults)
} }
private fun doCallTo(toastMessage: Int, call: (String?, Long?, String?) -> Call<List<Item>>) { private fun doCallTo(appendResults: Boolean, toastMessage: Int, call: (String?, Long?, String?) -> Call<List<Item>>) {
fun handleItemsResponse(response: Response<List<Item>>) { fun handleItemsResponse(response: Response<List<Item>>) {
val didUpdate = (response.body() != items) val didUpdate = (response.body() != items)
if (response.body() != null) { if (response.body() != null) {
if (response.body() != items) { if (response.body() != items) {
if (appendResults)
items.addAll(response.body() as ArrayList<Item>)
else
items = response.body() as ArrayList<Item> items = response.body() as ArrayList<Item>
} }
} else { } else {
if (!appendResults)
items = ArrayList() items = ArrayList()
} }
if (didUpdate) if (didUpdate)
handleListResult() handleListResult(appendResults)
mayBeEmpty() mayBeEmpty()
swipeRefreshLayout.isRefreshing = false swipeRefreshLayout.isRefreshing = false
} }
@ -616,22 +680,40 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
}) })
} }
private fun getUnRead() { private fun getUnRead(appendResults: Boolean = false) {
offset = if (appendResults) (offset + itemsNumber)
else 0
firstVisible = if (appendResults) firstVisible else 0
elementsShown = UNREAD_SHOWN elementsShown = UNREAD_SHOWN
doCallTo(R.string.cant_get_new_elements){t, id, f -> api.newItems(t, id, f, itemsNumber)} doCallTo(appendResults, R.string.cant_get_new_elements){t, id, f -> api.newItems(t, id, f, itemsNumber, offset)}
} }
private fun getRead() { private fun getRead(appendResults: Boolean = false) {
offset = if (appendResults) (offset + itemsNumber)
else 0
firstVisible = if (appendResults) firstVisible else 0
elementsShown = READ_SHOWN elementsShown = READ_SHOWN
doCallTo(R.string.cant_get_read){t, id, f -> api.readItems(t, id, f)} doCallTo(appendResults, R.string.cant_get_read){t, id, f -> api.readItems(t, id, f, itemsNumber, offset)}
} }
private fun getStarred() { private fun getStarred(appendResults: Boolean = false) {
offset = if (appendResults) (offset + itemsNumber)
else 0
firstVisible = if (appendResults) firstVisible else 0
elementsShown = FAV_SHOWN elementsShown = FAV_SHOWN
doCallTo(R.string.cant_get_favs){t, id, f -> api.starredItems(t, id, f)} doCallTo(appendResults, R.string.cant_get_favs){t, id, f -> api.starredItems(t, id, f, itemsNumber, offset)}
}
private fun handleListResult(appendResults: Boolean = false) {
if (appendResults) {
val oldManager = recyclerView.layoutManager
firstVisible = if ((oldManager is StaggeredGridLayoutManager)) {
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
}
else
(oldManager as GridLayoutManager)?.findFirstCompletelyVisibleItemPosition()
} }
private fun handleListResult() {
reloadLayoutManager() reloadLayoutManager()
val mAdapter: RecyclerView.Adapter<*> val mAdapter: RecyclerView.Adapter<*>
@ -664,6 +746,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
recyclerView.adapter = mAdapter recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
if (appendResults) {
recyclerView.scrollToPosition(firstVisible!!)
}
reloadBadges() reloadBadges()
} }

View File

@ -17,7 +17,7 @@ class IntroActivity : MaterialIntroActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
addSlide(SlideFragmentBuilder() addSlide(SlideFragmentBuilder()
.backgroundColor(R.color.colorPrimary) .backgroundColor(R.color.colorPrimary)

View File

@ -16,14 +16,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.* import android.widget.*
import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
@ -31,7 +23,12 @@ import apps.amine.bou.readerforselfoss.utils.checkAndDisplayStoreApk
import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid import apps.amine.bou.readerforselfoss.utils.isBaseUrlValid
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import io.fabric.sdk.android.Fabric import com.google.firebase.analytics.FirebaseAnalytics
import com.mikepenz.aboutlibraries.Libs
import com.mikepenz.aboutlibraries.LibsBuilder
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class LoginActivity : AppCompatActivity() { class LoginActivity : AppCompatActivity() {
@ -211,7 +208,7 @@ class LoginActivity : AppCompatActivity() {
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert) editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
editor.apply() editor.apply()
val api = SelfossApi(this, this@LoginActivity, isWithSelfSignedCert) val api = SelfossApi(this, this@LoginActivity, isWithSelfSignedCert, isWithSelfSignedCert)
api.login().enqueue(object : Callback<SuccessResponse> { api.login().enqueue(object : Callback<SuccessResponse> {
private fun preferenceError(t: Throwable) { private fun preferenceError(t: Throwable) {
editor.remove("url") editor.remove("url")

View File

@ -9,8 +9,11 @@ import android.widget.ImageView
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import com.anupcowkur.reservoir.Reservoir import com.anupcowkur.reservoir.Reservoir
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import com.github.stkent.amplify.feedback.DefaultEmailFeedbackCollector
import com.github.stkent.amplify.feedback.GooglePlayStoreFeedbackCollector
import com.github.stkent.amplify.tracking.Amplify import com.github.stkent.amplify.tracking.Amplify
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader
@ -48,7 +51,8 @@ class MyApp : MultiDexApplication() {
private fun initAmplify() { private fun initAmplify() {
Amplify.initSharedInstance(this) Amplify.initSharedInstance(this)
.setFeedbackEmailAddress(getString(R.string.feedback_email)) .setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
.setCriticalFeedbackCollectors(DefaultEmailFeedbackCollector(BuildConfig.FEEDBACK_EMAIL))
.applyAllDefaultRules() .applyAllDefaultRules()
} }
@ -63,11 +67,15 @@ class MyApp : MultiDexApplication() {
private fun initDrawerImageLoader() { private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() { DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) { override fun set(imageView: ImageView?, uri: Uri?, placeholder: Drawable?, tag: String?) {
Glide.with(imageView?.context).load(uri).placeholder(placeholder).into(imageView) Glide.with(imageView?.context)
.load(uri)
.apply(RequestOptions.fitCenterTransform()
.placeholder(placeholder))
.into(imageView)
} }
override fun cancel(imageView: ImageView?) { override fun cancel(imageView: ImageView?) {
Glide.clear(imageView) Glide.with(imageView?.context).clear(imageView)
} }
override fun placeholder(ctx: Context?, tag: String?): Drawable { override fun placeholder(ctx: Context?, tag: String?): Drawable {

View File

@ -1,7 +1,5 @@
package apps.amine.bou.readerforselfoss package apps.amine.bou.readerforselfoss
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -9,22 +7,21 @@ import android.view.ViewGroup
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.bumptech.glide.Glide
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import org.sufficientlysecure.htmltextview.HtmlTextView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi
import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent
import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.openItemUrl import apps.amine.bou.readerforselfoss.utils.openItemUrl
import apps.amine.bou.readerforselfoss.utils.shareLink import apps.amine.bou.readerforselfoss.utils.shareLink
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
import org.sufficientlysecure.htmltextview.HtmlHttpImageGetter
import org.sufficientlysecure.htmltextview.HtmlTextView
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import xyz.klinker.android.drag_dismiss.activity.DragDismissActivity
class ReaderActivity : DragDismissActivity() { class ReaderActivity : DragDismissActivity() {
@ -50,7 +47,7 @@ class ReaderActivity : DragDismissActivity() {
val title: TextView = v.findViewById(R.id.title) val title: TextView = v.findViewById(R.id.title)
val content: HtmlTextView = v.findViewById(R.id.content) val content: HtmlTextView = v.findViewById(R.id.content)
val url = intent.getStringExtra("url") val url = intent.getStringExtra("url")
val parser = MercuryApi(getString(R.string.mercury)) val parser = MercuryApi(BuildConfig.MERCURY_KEY)
val browserBtn: ImageButton = v.findViewById(R.id.browserBtn) val browserBtn: ImageButton = v.findViewById(R.id.browserBtn)
val shareBtn: ImageButton = v.findViewById(R.id.shareBtn) val shareBtn: ImageButton = v.findViewById(R.id.shareBtn)
@ -75,9 +72,9 @@ class ReaderActivity : DragDismissActivity() {
if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty()) if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isEmpty())
Glide Glide
.with(baseContext) .with(baseContext)
.load(response.body()!!.lead_image_url)
.asBitmap() .asBitmap()
.fitCenter() .load(response.body()!!.lead_image_url)
.apply(RequestOptions.fitCenterTransform())
.into(image) .into(image)
shareBtn.setOnClickListener { shareBtn.setOnClickListener {

View File

@ -40,7 +40,7 @@ class SourcesActivity : AppCompatActivity() {
val prefs = PreferenceManager.getDefaultSharedPreferences(this) val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val api = SelfossApi(this, this@SourcesActivity, prefs.getBoolean("isSelfSignedCert", false)) val api = SelfossApi(this, this@SourcesActivity, prefs.getBoolean("isSelfSignedCert", false), prefs.getBoolean("should_log_everything", false))
var items: ArrayList<Sources> = ArrayList() var items: ArrayList<Sources> = ArrayList()
mFab.attachToRecyclerView(mRecyclerView) mFab.attachToRecyclerView(mRecyclerView)

View File

@ -32,6 +32,9 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.themes.AppColors import apps.amine.bou.readerforselfoss.themes.AppColors
import apps.amine.bou.readerforselfoss.utils.* import apps.amine.bou.readerforselfoss.utils.*
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
import apps.amine.bou.readerforselfoss.utils.glide.bitmapFitCenter
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -63,7 +66,7 @@ class ItemCardAdapter(private val app: Activity,
holder.sourceTitleAndDate.text = itm.sourceAndDateText() holder.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(c).isEmpty()) { if (itm.getThumbnail(c).isEmpty()) {
Glide.clear(holder.itemImage) Glide.with(c).clear(holder.itemImage)
holder.itemImage.setImageDrawable(null) holder.itemImage.setImageDrawable(null)
} else { } else {
if (fullHeightCards) { if (fullHeightCards) {

View File

@ -29,6 +29,8 @@ import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.* import apps.amine.bou.readerforselfoss.utils.*
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop
import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlin.collections.ArrayList import kotlin.collections.ArrayList

View File

@ -21,7 +21,7 @@ import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi
import apps.amine.bou.readerforselfoss.api.selfoss.Sources import apps.amine.bou.readerforselfoss.api.selfoss.Sources
import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse
import apps.amine.bou.readerforselfoss.utils.circularBitmapDrawable import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable
import apps.amine.bou.readerforselfoss.utils.toTextDrawableString import apps.amine.bou.readerforselfoss.utils.toTextDrawableString

View File

@ -2,20 +2,20 @@ package apps.amine.bou.readerforselfoss.api.mercury
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.google.gson.annotations.SerializedName
class ParsedContent(@SerializedName("title") val title: String,
class ParsedContent(val title: String, @SerializedName("content") val content: String,
val content: String, @SerializedName("date_published") val date_published: String,
val date_published: String, @SerializedName("lead_image_url") val lead_image_url: String,
val lead_image_url: String, @SerializedName("dek") val dek: String,
val dek: String, @SerializedName("url") val url: String,
val url: String, @SerializedName("domain") val domain: String,
val domain: String, @SerializedName("excerpt") val excerpt: String,
val excerpt: String, @SerializedName("total_pages") val total_pages: Int,
val total_pages: Int, @SerializedName("rendered_pages") val rendered_pages: Int,
val rendered_pages: Int, @SerializedName("next_page_url") val next_page_url: String) : Parcelable {
val next_page_url: String) : Parcelable {
companion object { companion object {
@JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> { @JvmField val CREATOR: Parcelable.Creator<ParsedContent> = object : Parcelable.Creator<ParsedContent> {

View File

@ -2,11 +2,6 @@ package apps.amine.bou.readerforselfoss.api.selfoss
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.support.v7.app.AlertDialog
import android.widget.Toast
import apps.amine.bou.readerforselfoss.LoginActivity
import apps.amine.bou.readerforselfoss.R
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import com.burgstaller.okhttp.AuthenticationCacheInterceptor import com.burgstaller.okhttp.AuthenticationCacheInterceptor
@ -23,13 +18,14 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import java.security.cert.CertificateException import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
import java.security.cert.X509Certificate import okhttp3.logging.HttpLoggingInterceptor
import javax.net.ssl.*
// codebeat:disable[ARITY,TOO_MANY_FUNCTIONS] // codebeat:disable[ARITY,TOO_MANY_FUNCTIONS]
class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Boolean) { class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Boolean, shouldLog: Boolean) {
private lateinit var service: SelfossService private lateinit var service: SelfossService
private val config: Config = Config(c) private val config: Config = Config(c)
@ -38,36 +34,7 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder = fun OkHttpClient.Builder.maybeWithSelfSigned(isWithSelfSignedCert: Boolean): OkHttpClient.Builder =
if (isWithSelfSignedCert) { if (isWithSelfSignedCert) {
try { getUnsafeHttpClient()
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> =
arrayOf()
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
})
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val sslSocketFactory = sslContext.socketFactory
OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
} catch (e: Exception) {
throw RuntimeException(e)
}
} else { } else {
this this
} }
@ -78,14 +45,13 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
.with("basic", BasicAuthenticator(this)) .with("basic", BasicAuthenticator(this))
.build() .build()
fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient { fun DispatchingAuthenticator.getHttpClien(isWithSelfSignedCert: Boolean): OkHttpClient.Builder {
val authCache = ConcurrentHashMap<String, CachingAuthenticator>() val authCache = ConcurrentHashMap<String, CachingAuthenticator>()
return OkHttpClient return OkHttpClient
.Builder() .Builder()
.maybeWithSelfSigned(isWithSelfSignedCert) .maybeWithSelfSigned(isWithSelfSignedCert)
.authenticator(CachingAuthenticatorDecorator(this, authCache)) .authenticator(CachingAuthenticatorDecorator(this, authCache))
.addInterceptor(AuthenticationCacheInterceptor(authCache)) .addInterceptor(AuthenticationCacheInterceptor(authCache))
.build()
} }
@ -105,13 +71,23 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
.setLenient() .setLenient()
.create() .create()
val logging = HttpLoggingInterceptor()
logging.level = if (shouldLog)
HttpLoggingInterceptor.Level.BODY
else
HttpLoggingInterceptor.Level.NONE
val httpClient = authenticator.getHttpClien(isWithSelfSignedCert)
httpClient.addInterceptor(logging)
try { try {
val retrofit = val retrofit =
Retrofit Retrofit
.Builder() .Builder()
.baseUrl(config.baseUrl) .baseUrl(config.baseUrl)
.client(authenticator.getHttpClien(isWithSelfSignedCert)) .client(httpClient.build())
.addConverterFactory(GsonConverterFactory.create(gson)) .addConverterFactory(GsonConverterFactory.create(gson))
.build() .build()
service = retrofit.create(SelfossService::class.java) service = retrofit.create(SelfossService::class.java)
@ -123,17 +99,17 @@ class SelfossApi(c: Context, callingActivity: Activity, isWithSelfSignedCert: Bo
fun login(): Call<SuccessResponse> = fun login(): Call<SuccessResponse> =
service.loginToSelfoss(config.userLogin, config.userPassword) service.loginToSelfoss(config.userLogin, config.userPassword)
fun readItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> = fun readItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
getItems("read", tag, sourceId, search, 200) getItems("read", tag, sourceId, search, itemsNumber, offset)
fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int): Call<List<Item>> = fun newItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
getItems("unread", tag, sourceId, search, itemsNumber) getItems("unread", tag, sourceId, search, itemsNumber, offset)
fun starredItems(tag: String?, sourceId: Long?, search: String?): Call<List<Item>> = fun starredItems(tag: String?, sourceId: Long?, search: String?, itemsNumber: Int, offset: Int): Call<List<Item>> =
getItems("starred", tag, sourceId, search, 200) getItems("starred", tag, sourceId, search, itemsNumber, offset)
private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?, items: Int): Call<List<Item>> = private fun getItems(type: String, tag: String?, sourceId: Long?, search: String?, items: Int, offset: Int): Call<List<Item>> =
service.getItems(type, tag, sourceId, search, userName, password, items) service.getItems(type, tag, sourceId, search, userName, password, items, offset)
fun markItem(itemId: String): Call<SuccessResponse> = fun markItem(itemId: String): Call<SuccessResponse> =
service.markAsRead(itemId, userName, password) service.markAsRead(itemId, userName, password)

View File

@ -7,7 +7,7 @@ import android.os.Parcelable
import apps.amine.bou.readerforselfoss.utils.Config import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString
import com.google.gson.annotations.SerializedName
private fun constructUrl(config: Config?, path: String, file: String): String { private fun constructUrl(config: Config?, path: String, file: String): String {
@ -19,23 +19,28 @@ private fun constructUrl(config: Config?, path: String, file: String): String {
} }
data class Tag(val tag: String, val color: String, val unread: Int) data class Tag(@SerializedName("tag") val tag: String,
@SerializedName("color") val color: String,
@SerializedName("unread") val unread: Int)
class SuccessResponse(val success: Boolean) { class SuccessResponse(@SerializedName("success") val success: Boolean) {
val isSuccess: Boolean val isSuccess: Boolean
get() = success get() = success
} }
class Stats(val total: Int, val unread: Int, val starred: Int) class Stats(@SerializedName("total") val total: Int,
@SerializedName("unread") val unread: Int,
@SerializedName("starred") val starred: Int)
data class Spout(val name: String, val description: String) data class Spout(@SerializedName("name") val name: String,
@SerializedName("description") val description: String)
data class Sources(val id: String, data class Sources(@SerializedName("id") val id: String,
val title: String, @SerializedName("title") val title: String,
val tags: String, @SerializedName("tags") val tags: String,
val spout: String, @SerializedName("spout") val spout: String,
val error: String, @SerializedName("error") val error: String,
val icon: String) { @SerializedName("icon") val icon: String) {
var config: Config? = null var config: Config? = null
fun getIcon(app: Context): String { fun getIcon(app: Context): String {
@ -47,15 +52,15 @@ data class Sources(val id: String,
} }
data class Item(val id: String, data class Item(@SerializedName("id") val id: String,
val datetime: String, @SerializedName("datetime") val datetime: String,
val title: String, @SerializedName("title") val title: String,
val unread: Boolean, @SerializedName("unread") val unread: Boolean,
val starred: Boolean, @SerializedName("starred") val starred: Boolean,
val thumbnail: String, @SerializedName("thumbnail") val thumbnail: String,
val icon: String, @SerializedName("icon") val icon: String,
val link: String, @SerializedName("link") val link: String,
val sourcetitle: String) : Parcelable { @SerializedName("sourcetitle") val sourcetitle: String) : Parcelable {
var config: Config? = null var config: Config? = null
@ -109,14 +114,14 @@ data class Item(val id: String,
// TODO: maybe find a better way to handle these kind of urls // TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): String { fun getLinkDecoded(): String {
var stringUrl: String var stringUrl: String
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) { stringUrl = if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
if (link.contains("&amp;url=")) { if (link.contains("&amp;url=")) {
stringUrl = link.substringAfter("&amp;url=") link.substringAfter("&amp;url=")
} else { } else {
stringUrl = this.link.replace("&amp;", "&") this.link.replace("&amp;", "&")
} }
} else { } else {
stringUrl = this.link.replace("&amp;", "&") this.link.replace("&amp;", "&")
} }
// handle :443 => https // handle :443 => https

View File

@ -23,7 +23,8 @@ internal interface SelfossService {
@Query("search") search: String?, @Query("search") search: String?,
@Query("username") username: String, @Query("username") username: String,
@Query("password") password: String, @Query("password") password: String,
@Query("items") items: Int): Call<List<Item>> @Query("items") items: Int,
@Query("offset") offset: Int): Call<List<Item>>
@POST("mark/{id}") @POST("mark/{id}")
fun markAsRead(@Path("id") id: String, fun markAsRead(@Path("id") id: String,

View File

@ -27,6 +27,7 @@ import android.widget.Toast;
import java.util.List; import java.util.List;
import apps.amine.bou.readerforselfoss.BuildConfig;
import apps.amine.bou.readerforselfoss.R; import apps.amine.bou.readerforselfoss.R;
import apps.amine.bou.readerforselfoss.utils.Config; import apps.amine.bou.readerforselfoss.utils.Config;
import com.ftinc.scoop.ui.ScoopSettingsActivity; import com.ftinc.scoop.ui.ScoopSettingsActivity;
@ -242,7 +243,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { findPreference( "trackerLink" ).setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
openUrl(Uri.parse(getString(R.string.tracker_url))); openUrl(Uri.parse(BuildConfig.TRACKER_URL));
return true; return true;
} }
}); });
@ -250,7 +251,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { findPreference("sourceLink").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
openUrl(Uri.parse(getString(R.string.source_url))); openUrl(Uri.parse(BuildConfig.SOURCE_URL));
return false; return false;
} }
}); });

View File

@ -5,8 +5,6 @@ import android.content.Context
import android.support.annotation.ColorInt import android.support.annotation.ColorInt
import android.util.TypedValue import android.util.TypedValue
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import java.lang.reflect.AccessibleObject.setAccessible
class AppColors(a: Activity) { class AppColors(a: Activity) {

View File

@ -97,7 +97,7 @@ fun String.longHash(): Long {
val l = this.length val l = this.length
val chars = this.toCharArray() val chars = this.toCharArray()
for (i in 0..l - 1) { for (i in 0 until l) {
h = 31 * h + chars[i].toLong() h = 31 * h + chars[i].toLong()
} }
return h return h

View File

@ -1,24 +0,0 @@
package apps.amine.bou.readerforselfoss.utils
import android.content.Context
import android.graphics.Bitmap
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.BitmapImageViewTarget
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().centerCrop().into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().fitCenter().into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this).load(url).asBitmap().centerCrop().into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})

View File

@ -0,0 +1,39 @@
package apps.amine.bou.readerforselfoss.utils
import okhttp3.OkHttpClient
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager
fun getUnsafeHttpClient() =
try {
// Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> =
arrayOf()
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
})
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
val sslSocketFactory = sslContext.socketFactory
OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true }
} catch (e: Exception) {
throw RuntimeException(e)
}

View File

@ -16,7 +16,7 @@ fun String.toTextDrawableString(): String {
} }
fun Item.sourceAndDateText(): String { fun Item.sourceAndDateText(): String {
var formattedDate: String = try { val formattedDate: String = try {
" " + DateUtils.getRelativeTimeSpanString( " " + DateUtils.getRelativeTimeSpanString(
SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time, SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(this.datetime).time,
Date().time, Date().time,

View File

@ -7,14 +7,10 @@ import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
import apps.amine.bou.readerforselfoss.R import apps.amine.bou.readerforselfoss.R
import apps.amine.bou.readerforselfoss.ReaderActivity import apps.amine.bou.readerforselfoss.ReaderActivity
import apps.amine.bou.readerforselfoss.api.selfoss.Item
import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper
import xyz.klinker.android.drag_dismiss.DragDismissIntentBuilder
fun Context.buildCustomTabsIntent(): CustomTabsIntent { fun Context.buildCustomTabsIntent(): CustomTabsIntent {

View File

@ -0,0 +1,29 @@
package apps.amine.bou.readerforselfoss.utils.glide
import android.content.Context
import android.graphics.Bitmap
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget
fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
Glide.with(this).asBitmap().load(url).apply(RequestOptions.centerCropTransform()).into(iv)
fun Context.bitmapFitCenter(url: String, iv: ImageView) =
Glide.with(this).asBitmap().load(url).apply(RequestOptions.fitCenterTransform()).into(iv)
fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
Glide.with(this)
.asBitmap()
.load(url)
.apply(RequestOptions.centerCropTransform()).
into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap?) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, resource)
circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})

View File

@ -0,0 +1,32 @@
package apps.amine.bou.readerforselfoss.utils.glide
import android.content.Context
import apps.amine.bou.readerforselfoss.utils.Config
import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.module.GlideModule
import com.bumptech.glide.load.model.GlideUrl
import java.io.InputStream
class SelfSignedGlideModule : GlideModule {
override fun applyOptions(context: Context?, builder: GlideBuilder?) {}
override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) {
if (context != null) {
val pref = context?.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
if (pref.getBoolean("isSelfSignedCert", false)) {
val client = getUnsafeHttpClient().build()
registry?.append(GlideUrl::class.java, InputStream::class.java,
com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader.Factory(client))
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

View File

@ -145,4 +145,10 @@
<string name="read_debug_off">Aucun log quand un article est marqué comme lu</string> <string name="read_debug_off">Aucun log quand un article est marqué comme lu</string>
<string name="summary_debug_identifier">Identifiant de debug</string> <string name="summary_debug_identifier">Identifiant de debug</string>
<string name="unique_id_to_clipboard">Texte copié</string> <string name="unique_id_to_clipboard">Texte copié</string>
<string name="display_header_drawer_summary">Afficher une entête avec l\'url de votre instance de Selfoss en haut du drawer lateral.</string>
<string name="display_header_drawer_title">Entête de compte</string>
<string name="login_everything_title">Log de tous les appels à l\'API</string>
<string name="login_everything_off">Aucun appel à l\'API ne sera logué</string>
<string name="login_everything_on">Tous les appels à l\'API vont êtres logués</string>
<string name="pref_general_infinite_loading_title">(BETA) Charger plus d\'articles au scroll</string>
</resources> </resources>

View File

@ -145,4 +145,11 @@
<string name="read_debug_off">No log when marking an item as read</string> <string name="read_debug_off">No log when marking an item as read</string>
<string name="summary_debug_identifier">Debug identifier</string> <string name="summary_debug_identifier">Debug identifier</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string> <string name="unique_id_to_clipboard">Identifier copied to your clipboard</string>
<string
name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string>
<string name="display_header_drawer_title">Account header</string>
<string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_off">No api call will be logged</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string>
</resources> </resources>

View File

@ -147,4 +147,11 @@
<string name="read_debug_on">Api calls will be logged when marking an article as read</string> <string name="read_debug_on">Api calls will be logged when marking an article as read</string>
<string name="summary_debug_identifier">Debug identifier</string> <string name="summary_debug_identifier">Debug identifier</string>
<string name="unique_id_to_clipboard">Identifier copied to your clipboard</string> <string name="unique_id_to_clipboard">Identifier copied to your clipboard</string>
<string
name="display_header_drawer_summary">Display a header with the selfoss instance url on the lateral drawer.</string>
<string name="display_header_drawer_title">Account header</string>
<string name="login_everything_title">Logging every api calls</string>
<string name="login_everything_on">This will log every api call for debug purpose.</string>
<string name="login_everything_off">No api call will be logged</string>
<string name="pref_general_infinite_loading_title">(BETA) Load more articles on scroll</string>
</resources> </resources>

View File

@ -13,6 +13,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarDark" parent="MaterialDrawerTheme"> <style name="NoBarDark" parent="MaterialDrawerTheme">
@ -25,12 +26,14 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<!-- ToolBar --> <!-- ToolBar -->
<style name="ToolBarStyle" parent="Theme.AppCompat"> <style name="ToolBarStyle" parent="Theme.AppCompat">
<item name="android:textColorPrimary">@color/white</item> <item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/white</item> <item name="android:textColorSecondary">@color/white</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
<item name="actionMenuTextColor">@color/white</item> <item name="actionMenuTextColor">@color/white</item>
<!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item> <!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>--> <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>-->
@ -46,6 +49,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarBlueAmberDark" parent="MaterialDrawerTheme"> <style name="NoBarBlueAmberDark" parent="MaterialDrawerTheme">
@ -58,6 +62,7 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarGreyOrange" parent="MaterialDrawerTheme.Light"> <style name="NoBarGreyOrange" parent="MaterialDrawerTheme.Light">
@ -69,6 +74,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarGreyOrangeDark" parent="MaterialDrawerTheme"> <style name="NoBarGreyOrangeDark" parent="MaterialDrawerTheme">
@ -81,6 +87,7 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarIndigoPink" parent="MaterialDrawerTheme.Light"> <style name="NoBarIndigoPink" parent="MaterialDrawerTheme.Light">
@ -92,6 +99,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarIndigoPinkDark" parent="MaterialDrawerTheme"> <style name="NoBarIndigoPinkDark" parent="MaterialDrawerTheme">
@ -104,6 +112,7 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarRedTeal" parent="MaterialDrawerTheme.Light"> <style name="NoBarRedTeal" parent="MaterialDrawerTheme.Light">
@ -115,6 +124,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarRedTealDark" parent="MaterialDrawerTheme"> <style name="NoBarRedTealDark" parent="MaterialDrawerTheme">
@ -127,6 +137,7 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarCyanPink" parent="MaterialDrawerTheme.Light"> <style name="NoBarCyanPink" parent="MaterialDrawerTheme.Light">
@ -138,6 +149,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarCyanPinkDark" parent="MaterialDrawerTheme"> <style name="NoBarCyanPinkDark" parent="MaterialDrawerTheme">
@ -150,6 +162,7 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
@ -162,6 +175,7 @@
<item name="android:colorBackground">@color/md_grey_50</item> <item name="android:colorBackground">@color/md_grey_50</item>
<item name="android:textColorPrimary">@color/md_grey_900</item> <item name="android:textColorPrimary">@color/md_grey_900</item>
<item name="android:textColorSecondary">@color/md_grey_400</item> <item name="android:textColorSecondary">@color/md_grey_400</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
<style name="NoBarTealOrangeDark" parent="MaterialDrawerTheme"> <style name="NoBarTealOrangeDark" parent="MaterialDrawerTheme">
@ -174,6 +188,7 @@
<item name="bnbBackgroundColor">@color/md_grey_900</item> <item name="bnbBackgroundColor">@color/md_grey_900</item>
<item name="android:textColorPrimary">@color/md_white_1000</item> <item name="android:textColorPrimary">@color/md_white_1000</item>
<item name="android:textColorSecondary">@color/md_grey_600</item> <item name="android:textColorSecondary">@color/md_grey_600</item>
<item name="material_drawer_header_selection_text">@color/md_grey_900</item>
</style> </style>
</resources> </resources>

View File

@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="false"
android:key="should_log_everything"
android:summaryOff="@string/login_everything_off"
android:summaryOn="@string/login_everything_on"
android:title="@string/login_everything_title" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="loging_debug" android:key="loging_debug"

View File

@ -1,5 +1,4 @@
<PreferenceScreen xmlns:tools="http://schemas.android.com/tools" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android">
<!--<SwitchPreference <!--<SwitchPreference
android:defaultValue="false" android:defaultValue="false"
@ -18,6 +17,11 @@
android:selectAllOnFocus="true" android:selectAllOnFocus="true"
android:singleLine="true" android:singleLine="true"
android:title="@string/pref_api_items_number_title" /> android:title="@string/pref_api_items_number_title" />
<SwitchPreference
android:defaultValue="false"
android:key="infinite_loading"
android:title="@string/pref_general_infinite_loading_title" />
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_general_category_links"> android:title="@string/pref_general_category_links">
@ -39,6 +43,11 @@
android:title="@string/pref_general_category_displaying"> android:title="@string/pref_general_category_displaying">
</PreferenceCategory> </PreferenceCategory>
<SwitchPreference
android:defaultValue="false"
android:key="account_header_displaying"
android:summary="@string/display_header_drawer_summary"
android:title="@string/display_header_drawer_title" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="card_view_active" android:key="card_view_active"

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = '1.1.3-2' ext.kotlin_version = '1.1.4-3'
repositories { repositories {
maven { url 'https://maven.google.com' }
jcenter() jcenter()
google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.0-beta2' classpath 'com.android.tools.build:gradle:3.0.0-beta5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -1,6 +1,6 @@
#Wed Aug 16 19:45:22 CEST 2017 #Sat Sep 02 11:43:17 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-rc-1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

Binary file not shown.