Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
FreshRSS-Android
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
30
Issues
30
List
Boards
Labels
Service Desk
Milestones
Merge Requests
2
Merge Requests
2
Requirements
Requirements
List
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Operations
Operations
Environments
Packages & Registries
Packages & Registries
Package Registry
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issue
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Christophe Henry
FreshRSS-Android
Commits
4438b0f6
Commit
4438b0f6
authored
Feb 27, 2020
by
Christophe Henry
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'drop-fuel' into 'develop'
Replace Fuel by Retrofit See merge request
!79
parents
16649ec0
4b460ec9
Pipeline
#3684
passed with stage
in 0 seconds
Changes
31
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
1221 additions
and
399 deletions
+1221
-399
app/build.gradle
app/build.gradle
+31
-8
app/src/main/java/fr/chenry/android/freshrss/RefresherService.kt
.../main/java/fr/chenry/android/freshrss/RefresherService.kt
+1
-6
app/src/main/java/fr/chenry/android/freshrss/activities/LoginActivity.kt
...va/fr/chenry/android/freshrss/activities/LoginActivity.kt
+17
-9
app/src/main/java/fr/chenry/android/freshrss/components/subscriptionarticles/SubscriptionArticlesDetailFragment.kt
...ubscriptionarticles/SubscriptionArticlesDetailFragment.kt
+1
-1
app/src/main/java/fr/chenry/android/freshrss/components/subscriptionarticles/SubscriptionArticlesFragment.kt
...ents/subscriptionarticles/SubscriptionArticlesFragment.kt
+1
-1
app/src/main/java/fr/chenry/android/freshrss/components/subscriptions/SubscriptionsFragment.kt
...reshrss/components/subscriptions/SubscriptionsFragment.kt
+1
-1
app/src/main/java/fr/chenry/android/freshrss/store/Store.kt
app/src/main/java/fr/chenry/android/freshrss/store/Store.kt
+133
-98
app/src/main/java/fr/chenry/android/freshrss/store/api/API.kt
...src/main/java/fr/chenry/android/freshrss/store/api/API.kt
+81
-0
app/src/main/java/fr/chenry/android/freshrss/store/api/Api.kt
...src/main/java/fr/chenry/android/freshrss/store/api/Api.kt
+0
-93
app/src/main/java/fr/chenry/android/freshrss/store/api/Endpoints.kt
...in/java/fr/chenry/android/freshrss/store/api/Endpoints.kt
+0
-16
app/src/main/java/fr/chenry/android/freshrss/store/api/models/ContentItem.kt
...r/chenry/android/freshrss/store/api/models/ContentItem.kt
+1
-0
app/src/main/java/fr/chenry/android/freshrss/store/api/models/SerDeser.kt
...a/fr/chenry/android/freshrss/store/api/models/SerDeser.kt
+0
-65
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/APIConverter.kt
...r/chenry/android/freshrss/store/api/utils/APIConverter.kt
+28
-0
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/APIInterceptor.kt
...chenry/android/freshrss/store/api/utils/APIInterceptor.kt
+73
-0
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/Annotations.kt
...fr/chenry/android/freshrss/store/api/utils/Annotations.kt
+47
-0
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/Serializers.kt
...fr/chenry/android/freshrss/store/api/utils/Serializers.kt
+85
-0
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/ServerException.kt
...henry/android/freshrss/store/api/utils/ServerException.kt
+17
-0
app/src/main/java/fr/chenry/android/freshrss/store/database/FreshRSSDabatabase.kt
...nry/android/freshrss/store/database/FreshRSSDabatabase.kt
+2
-2
app/src/main/java/fr/chenry/android/freshrss/store/database/models/Account.kt
.../chenry/android/freshrss/store/database/models/Account.kt
+15
-22
app/src/main/java/fr/chenry/android/freshrss/utils/KotlinExtensions.kt
...java/fr/chenry/android/freshrss/utils/KotlinExtensions.kt
+8
-66
app/src/test/java/fr/chenry/android/freshrss/BaseTest.kt
app/src/test/java/fr/chenry/android/freshrss/BaseTest.kt
+7
-0
app/src/test/java/fr/chenry/android/freshrss/store/api/APITest.kt
...test/java/fr/chenry/android/freshrss/store/api/APITest.kt
+355
-0
app/src/test/java/fr/chenry/android/freshrss/store/api/models/ContentItemsHandlerTest.kt
...roid/freshrss/store/api/models/ContentItemsHandlerTest.kt
+5
-8
app/src/test/java/fr/chenry/android/freshrss/utils/matchers/ContentItemsMatchers.kt
...y/android/freshrss/utils/matchers/ContentItemsMatchers.kt
+82
-0
app/src/test/resources/fixtures/APIFixtures/stream-contents.json
.../test/resources/fixtures/APIFixtures/stream-contents.json
+54
-0
app/src/test/resources/fixtures/APIFixtures/subscriptions.json
...rc/test/resources/fixtures/APIFixtures/subscriptions.json
+69
-0
app/src/test/resources/fixtures/APIFixtures/tags.json
app/src/test/resources/fixtures/APIFixtures/tags.json
+19
-0
app/src/test/resources/fixtures/APIFixtures/unread-contents.json
.../test/resources/fixtures/APIFixtures/unread-contents.json
+55
-0
app/src/test/resources/fixtures/APIFixtures/unread-count.json
...src/test/resources/fixtures/APIFixtures/unread-count.json
+30
-0
build.gradle
build.gradle
+1
-1
gradle/wrapper/gradle-wrapper.properties
gradle/wrapper/gradle-wrapper.properties
+2
-2
No files found.
app/build.gradle
View file @
4438b0f6
...
...
@@ -31,6 +31,7 @@ android {
kotlinOptions
{
jvmTarget
=
"1.8"
freeCompilerArgs
=
[
"-Xallow-result-return-type"
]
}
testOptions
{
...
...
@@ -102,9 +103,9 @@ spotless {
dependencies
{
def
appcompat_version
=
"1.1.0"
def
activity_version
=
"1.1.0"
def
fragment_version
=
"1.2.1"
def
fragment_version
=
'1.2.2'
def
lifecycle_version
=
"2.2.0"
def
room_version
=
"2.2.3"
def
room_version
=
'2.2.4'
def
roomigrant_version
=
"0.1.7"
def
fuel_version
=
"2.0.1"
def
jackson_version
=
'2.10.2'
...
...
@@ -115,6 +116,7 @@ dependencies {
def
acraVersion
=
'5.5.0'
def
autoservice_version
=
"1.0-rc6"
def
android_test
=
"1.2.0"
def
retrofit_version
=
"2.7.2"
// Linter
ktlint
"com.github.shyiko:ktlint:0.31.0"
...
...
@@ -136,10 +138,10 @@ dependencies {
implementation
"androidx.constraintlayout:constraintlayout:1.1.3"
implementation
"androidx.recyclerview:recyclerview:1.1.0"
implementation
"androidx.preference:preference-ktx:1.1.0"
implementation
"com.google.android.material:material:1.2.0-alpha04"
implementation
'com.google.android.material:material:1.2.0-alpha05'
// AndroidX testing
i
mplementation
"androidx.fragment:fragment-testing:$fragment_version"
debugI
mplementation
"androidx.fragment:fragment-testing:$fragment_version"
// ViewModel and LiveData
implementation
"androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
...
...
@@ -163,13 +165,13 @@ dependencies {
kapt
"com.github.MatrixDev.Roomigrant:RoomigrantCompiler:$roomigrant_version"
// HTTP and promises
implementation
"com.github.kittinunf.fuel:fuel:$fuel_version"
implementation
"com.github.kittinunf.fuel:fuel-android:$fuel_version"
implementation
(
"com.github.kittinunf.fuel:fuel-jackson:$fuel_version"
)
{
transitive
=
false
}
implementation
"com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version"
implementation
"com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version"
implementation
"nl.komponents.kovenant:kovenant:$promise_version"
implementation
"nl.komponents.kovenant:kovenant-android:$promise_version"
implementation
"com.squareup.retrofit2:retrofit:$retrofit_version"
implementation
"com.squareup.retrofit2:converter-jackson:$retrofit_version"
implementation
"com.squareup.retrofit2:converter-scalars:$retrofit_version"
// Utils
implementation
"org.apache.commons:commons-text:1.8"
...
...
@@ -188,12 +190,33 @@ dependencies {
// Tests
testImplementation
"junit:junit:4.13"
testImplementation
"org.hamcrest:hamcrest-library:2.2"
testImplementation
"com.squareup.okhttp3:mockwebserver:4.4.0"
testImplementation
"com.github.javafaker:javafaker:1.0.2"
/*
* org.json:json is used with explicit permission of its copyright holders :
*
* Copyright (c) 2002 JSON.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* The Software shall be used for Good, not Evil.
*/
testImplementation
"org.json:json:20190722"
androidTestImplementation
"com.github.javafaker:javafaker:1.0.2"
androidTestImplementation
"androidx.test:rules:$android_test"
androidTestImplementation
"androidx.test:runner:$android_test"
androidTestImplementation
"androidx.test.ext:junit:1.1.1"
androidTestImplementation
"androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation
"androidx.test.espresso:espresso-contrib:$espresso_version"
androidTestImplementation
"com.github.javafaker:javafaker:1.0.2"
// Debug
debugImplementation
"com.facebook.stetho:stetho:1.5.1"
...
...
app/src/main/java/fr/chenry/android/freshrss/RefresherService.kt
View file @
4438b0f6
...
...
@@ -10,7 +10,6 @@ import androidx.core.app.NotificationCompat
import
fr.chenry.android.freshrss.R.drawable
import
fr.chenry.android.freshrss.R.string
import
fr.chenry.android.freshrss.store.Store
import
fr.chenry.android.freshrss.store.database.models.VoidAccount
import
fr.chenry.android.freshrss.utils.*
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.runBlocking
...
...
@@ -49,9 +48,6 @@ class RefresherService: Service() {
fun
refresh
(
manual
:
Boolean
=
true
):
Promise
<
Unit
,
Exception
>
{
if
(
Store
.
refreshingPromise
.
value
!=
null
)
return
Store
.
refreshingPromise
.
value
!!
// This case should not happen. Blocking only for testing
if
((
Store
.
account
.
value
?:
VoidAccount
)
==
VoidAccount
)
return
Promise
.
ofSuccess
(
Unit
)
if
(!
F
.
context
.
isConnectedToNetwork
())
{
if
(
manual
)
{
Toast
.
makeText
(
...
...
@@ -66,8 +62,6 @@ class RefresherService: Service() {
F
.
notificationManager
.
notify
(
NotificationHelper
.
ONGOING_REFRESH_NOTIFICATION
,
refreshNotification
)
Store
.
ensureToken
()
val
promise
=
Store
.
getSubscriptions
()
.
bind
{
Store
.
getUnreadCount
()}
.
bind
{
...
...
@@ -82,6 +76,7 @@ class RefresherService: Service() {
Store
.
refreshingPromise
.
postValue
(
promise
)
promise
.
always
{
Store
.
refreshingPromise
.
postValue
(
null
)}
.
failUi
{
it
.
printStackTrace
()
F
.
notificationManager
.
notify
(
NotificationHelper
.
FAIL_REFRESH_NOTIFICATION
,
failNotification
)
}.
alwaysUi
{
cancelRefreshNotification
()
...
...
app/src/main/java/fr/chenry/android/freshrss/activities/LoginActivity.kt
View file @
4438b0f6
...
...
@@ -14,10 +14,9 @@ import androidx.appcompat.app.AppCompatActivity
import
androidx.core.widget.addTextChangedListener
import
fr.chenry.android.freshrss.R
import
fr.chenry.android.freshrss.store.Store
import
fr.chenry.android.freshrss.store.api.utils.ServerException
import
fr.chenry.android.freshrss.utils.*
import
kotlinx.android.synthetic.main.activity_login.*
import
nl.komponents.kovenant.ui.failUi
import
nl.komponents.kovenant.ui.successUi
import
java.util.Properties
class
LoginActivity
:
AppCompatActivity
()
{
...
...
@@ -106,14 +105,10 @@ class LoginActivity: AppCompatActivity() {
focusView
?.
requestFocus
()
}
else
{
showProgress
(
true
)
Store
.
login
(
instanceUrl
.
toString
(),
loginStr
,
passwordStr
)
successUi
{
startActivity
(
Intent
(
this
,
MainActivity
::
class
.
java
))
Store
.
login
(
instanceUrl
.
toString
(),
loginStr
,
passwordStr
)
.
onSuccess
{
startActivity
(
Intent
(
this
@LoginActivity
,
MainActivity
::
class
.
java
))
finish
()
}
failUi
{
this
.
e
(
it
)
instance
.
error
=
getString
(
R
.
string
.
error_instance
)
showProgress
(
false
)
}
}.
onFailure
(
::
handleError
)
}
}
...
...
@@ -127,6 +122,19 @@ class LoginActivity: AppCompatActivity() {
reified_instance_url
.
text
=
instanceUrl
.
toSpanString
()
}
private
fun
handleError
(
err
:
Throwable
)
{
showProgress
(
false
)
this
.
e
(
err
)
instance
.
error
=
getString
(
R
.
string
.
error_instance
)
if
(
err
is
ServerException
)
{
when
(
err
.
response
.
code
())
{
403
->
{}
404
->
{}
else
->
{}
}
}
}
private
fun
resetErrors
()
{
listOf
(
login
,
password
,
instance
).
forEach
{
it
.
error
=
null
...
...
app/src/main/java/fr/chenry/android/freshrss/components/subscriptionarticles/SubscriptionArticlesDetailFragment.kt
View file @
4438b0f6
...
...
@@ -27,7 +27,7 @@ import nl.komponents.kovenant.ui.failUi
class
SubscriptionArticlesDetailFragment
:
Fragment
()
{
private
lateinit
var
articleId
:
String
private
val
model
:
SubscriptionArticleVM
by
lazy
{
ViewModelProvider
(
activity
!!
.
viewModelStore
,
SubscriptionArticleVMF
(
articleId
))
ViewModelProvider
(
requireActivity
()
.
viewModelStore
,
SubscriptionArticleVMF
(
articleId
))
.
get
(
"articleId=$articleId"
,
SubscriptionArticleVM
::
class
.
java
)
}
private
var
isFetching
=
MutableLiveData
<
Boolean
>().
apply
{
value
=
false
}
...
...
app/src/main/java/fr/chenry/android/freshrss/components/subscriptionarticles/SubscriptionArticlesFragment.kt
View file @
4438b0f6
...
...
@@ -29,7 +29,7 @@ class SubscriptionArticlesFragment: Fragment() {
private
val
readStatus
:
ReadStatus
by
lazy
{
args
.
readStatus
}
private
val
model
:
SubscriptionArticlesVM
by
lazy
{
ViewModelProvider
(
activity
!!
.
viewModelStore
,
SubscriptionArticlesVMF
(
subscription
,
readStatus
))
ViewModelProvider
(
requireActivity
()
.
viewModelStore
,
SubscriptionArticlesVMF
(
subscription
,
readStatus
))
.
get
(
"stream=${subscription.id}:readStatus=$readStatus"
,
SubscriptionArticlesVM
::
class
.
java
)
}
...
...
app/src/main/java/fr/chenry/android/freshrss/components/subscriptions/SubscriptionsFragment.kt
View file @
4438b0f6
...
...
@@ -32,7 +32,7 @@ class SubscriptionsFragment: Fragment(), Observer<SubscriptionViewItems> {
val
model
by
lazy
{
val
key
=
"section=${section.name}${if(category != VoidCategory) "
:
category
=
$
{
category
.
id
}
" else ""}"
ViewModelProvider
(
activity
!!
.
viewModelStore
,
SubscriptionsFragmentCategoryVMF
(
category
,
section
))
ViewModelProvider
(
requireActivity
()
.
viewModelStore
,
SubscriptionsFragmentCategoryVMF
(
category
,
section
))
.
get
(
key
,
SubscriptionsVM
::
class
.
java
)
}
...
...
app/src/main/java/fr/chenry/android/freshrss/store/Store.kt
View file @
4438b0f6
This diff is collapsed.
Click to expand it.
app/src/main/java/fr/chenry/android/freshrss/store/api/API.kt
0 → 100644
View file @
4438b0f6
package
fr.chenry.android.freshrss.store.api
import
fr.chenry.android.freshrss.store.api.models.*
import
fr.chenry.android.freshrss.store.api.utils.*
import
fr.chenry.android.freshrss.store.database.models.Account
import
fr.chenry.android.freshrss.store.database.models.ReadStatus
import
fr.chenry.android.freshrss.utils.addTrailingSlash
import
okhttp3.OkHttpClient
import
retrofit2.*
import
retrofit2.converter.jackson.JacksonConverterFactory
import
retrofit2.converter.scalars.ScalarsConverterFactory
import
retrofit2.http.*
const
val
LOGIN_ENPOINT
=
"accounts/ClientLogin"
interface
API
{
@AuthorisationRequired
@POST
(
"reader/api/0/token"
)
fun
getWriteToken
():
Call
<
String
>
@JsonOutput
@AuthorisationRequired
@UseDeserializer
(
SubscriptionApiItemsConverter
::
class
)
@GET
(
"reader/api/0/subscription/list"
)
fun
getSubscriptions
():
Call
<
List
<
SubscriptionApiItem
>>
@JsonOutput
@AuthorisationRequired
@GET
(
"reader/api/0/unread-count"
)
fun
getUnreadCount
():
Call
<
UnreadCountsHandler
>
@JsonOutput
@AuthorisationRequired
@GET
(
"reader/api/0/stream/contents/{id}"
)
fun
getStreamContents
(
@Path
(
"id"
)
id
:
String
):
Call
<
ContentItemsHandler
>
@JsonOutput
@AuthorisationRequired
@UseDeserializer
(
TagsConverter
::
class
)
@GET
(
"reader/api/0/tag/list"
)
fun
getTags
():
Call
<
List
<
String
>>
@JsonOutput
@AuthorisationRequired
@GET
(
"reader/api/0/stream/contents/user/-/state/com.google/reading-list?n=1000000&xt=user/-/state/com.google/read"
)
fun
getUnreadItems
(
@Query
(
"ot"
)
@QueryTransformer
(
OTTransformer
::
class
)
ot
:
Long
):
Call
<
ContentItemsHandler
>
@TokenRequired
@AuthorisationRequired
@POST
(
"reader/api/0/edit-tag"
)
fun
postReadStatus
(
@Query
(
"i"
)
itemId
:
String
,
@Query
(
"r"
)
@QueryTransformer
(
ReadStatusTransformer
::
class
)
readStatus
:
ReadStatus
):
Call
<
Unit
>
@TokenRequired
@AuthorisationRequired
@POST
(
"reader/api/0/subscription/quickadd"
)
fun
postAddSubscription
(
@Query
(
"quickadd"
)
url
:
String
):
Call
<
String
>
companion
object
{
const
val
TOKEN_RENEWAL_FAILED_MESSAGE
=
"Token renewal failed"
const
val
VOID_ACCOUNT_FAIL_MESSAGE
=
"No account attached"
fun
get
(
account
:
Account
):
API
{
val
interceptor
=
APIInterceptor
(
account
)
val
okHttpClient
=
OkHttpClient
.
Builder
().
addInterceptor
(
interceptor
).
build
()
val
service
:
API
=
Retrofit
.
Builder
()
.
client
(
okHttpClient
)
.
baseUrl
(
account
.
serverInstance
.
addTrailingSlash
())
.
addConverterFactory
(
APIConverter
())
.
addConverterFactory
(
ScalarsConverterFactory
.
create
())
.
addConverterFactory
(
JacksonConverterFactory
.
create
(
JACKSON_OBJECT_MAPPER
))
.
build
()
.
create
()
interceptor
.
service
=
service
return
service
}
}
}
app/src/main/java/fr/chenry/android/freshrss/store/api/Api.kt
deleted
100644 → 0
View file @
16649ec0
package
fr.chenry.android.freshrss.store.api
import
com.github.kittinunf.fuel.Fuel
import
fr.chenry.android.freshrss.store.api.models.*
import
fr.chenry.android.freshrss.store.database.models.*
import
fr.chenry.android.freshrss.utils.*
import
nl.komponents.kovenant.*
class
Api
(
val
account
:
Account
)
{
private
val
endpoints
=
Endpoints
(
account
.
serverInstance
)
fun
getWriteToken
()
=
Fuel
.
post
(
endpoints
.
tokenEndpoint
)
.
header
(
"Authorization"
to
"GoogleLogin auth=${account.SID}"
)
.
promiseString
()
fun
getSubscriptions
():
Promise
<
List
<
SubscriptionApiItem
>,
Exception
>
=
Fuel
.
getJson
(
endpoints
.
subscriptionEndpoint
)
.
authentify
(
this
.
account
)
.
promise
<
SubscriptionsHandler
>()
.
then
{
it
.
subscriptions
}
fun
getUnreadCount
():
Promise
<
UnreadCountsHandler
,
Exception
>
=
Fuel
.
getJson
(
endpoints
.
unreadCountEndpoint
)
.
authentify
(
this
.
account
)
.
promise
()
fun
getStreamContents
(
id
:
String
):
Promise
<
ContentItemsHandler
,
Exception
>
=
Fuel
.
getJson
(
endpoints
.
streamContentsEndpoint
(
id
),
listOf
(
"n"
to
1_000_000
))
.
authentify
(
this
.
account
)
.
promise
()
fun
getTags
():
Promise
<
List
<
String
>,
Exception
>
=
Fuel
.
getJson
(
endpoints
.
tagEndpoint
)
.
authentify
(
this
.
account
)
.
promiseWithSerializer
(
TagsDeserializer
)
fun
getUnreadItems
(
olderTimestamp
:
Long
):
Promise
<
ContentItemsHandler
,
Exception
>
{
val
params
=
mutableListOf
(
"xt"
to
"user/-/state/com.google/read"
,
"n"
to
1_000_000
)
if
(
olderTimestamp
>
0
)
params
.
add
(
"ot"
to
"$olderTimestamp"
)
return
Fuel
.
getJson
(
endpoints
.
unreadItemsEndpoint
,
params
)
.
authentify
(
account
)
.
promise
()
}
fun
postReadStatus
(
itemId
:
String
,
readStatus
:
ReadStatus
):
Promise
<
Unit
,
Exception
>
{
val
parameters
=
listOf
(
"i"
to
itemId
,
(
if
(
readStatus
==
ReadStatus
.
READ
)
"a"
else
"r"
)
to
"user/-/state/com.google/read"
)
return
Fuel
.
post
(
endpoints
.
editTagEnpoint
,
parameters
)
.
authentify
(
account
)
.
authorize
(
account
)
.
promiseString
()
.
toSuccessVoid
()
}
fun
postAddSubscription
(
url
:
String
):
Promise
<
String
,
Exception
>
=
Fuel
.
post
(
endpoints
.
subscriptionAdd
(
url
))
.
authentify
(
account
)
.
authorize
(
account
)
.
promiseWithSerializer
(
SubscritpionAddResponseDeserializer
)
companion
object
{
fun
login
(
instance
:
String
,
login
:
String
,
password
:
String
):
Promise
<
Account
,
Exception
>
{
val
params
=
listOf
(
"service"
to
"reader"
,
"Email"
to
login
,
"Passwd"
to
password
,
"source"
to
"FreshRSS"
,
"accountType"
to
"GOOGLE"
)
val
temporaryEndpoints
=
Endpoints
(
instance
)
return
Fuel
.
post
(
temporaryEndpoints
.
loginEndpoint
,
params
)
.
promiseWithSerializer
<
Account
>()
.
then
{
it
.
copy
(
serverInstance
=
instance
,
login
=
login
)
}
}
}
}
app/src/main/java/fr/chenry/android/freshrss/store/api/Endpoints.kt
deleted
100644 → 0
View file @
16649ec0
package
fr.chenry.android.freshrss.store.api
import
java.net.URLEncoder
class
Endpoints
(
private
val
base
:
String
)
{
val
loginEndpoint
=
"${this.base}/accounts/ClientLogin"
val
tokenEndpoint
=
"${this.base}/reader/api/0/token"
val
subscriptionEndpoint
=
"${this.base}/reader/api/0/subscription/list"
fun
subscriptionAdd
(
url
:
String
)
=
"${this.base}/reader/api/0/subscription/quickadd?quickadd=${URLEncoder.encode(url, "
utf-8
")}"
val
unreadCountEndpoint
=
"${this.base}/reader/api/0/unread-count"
val
unreadItemsEndpoint
=
"${this.base}/reader/api/0/stream/contents/user/-/state/com.google/reading-list"
fun
streamContentsEndpoint
(
id
:
String
)
=
"${this.base}/reader/api/0/stream/contents/$id"
val
tagEndpoint
=
"${this.base}/reader/api/0/tag/list"
val
editTagEnpoint
=
"${this.base}/reader/api/0/edit-tag"
}
app/src/main/java/fr/chenry/android/freshrss/store/api/models/ContentItem.kt
View file @
4438b0f6
...
...
@@ -2,6 +2,7 @@ package fr.chenry.android.freshrss.store.api.models
import
com.fasterxml.jackson.annotation.JsonProperty
import
com.fasterxml.jackson.databind.annotation.JsonDeserialize
import
fr.chenry.android.freshrss.store.api.utils.*
import
org.joda.time.LocalDateTime
data class
ContentItemsHandler
(
...
...
app/src/main/java/fr/chenry/android/freshrss/store/api/models/SerDeser.kt
deleted
100644 → 0
View file @
16649ec0
package
fr.chenry.android.freshrss.store.api.models
import
com.fasterxml.jackson.core.JsonParser
import
com.fasterxml.jackson.databind.DeserializationContext
import
com.fasterxml.jackson.databind.JsonDeserializer
import
com.fasterxml.jackson.databind.node.ArrayNode
import
com.fasterxml.jackson.datatype.joda.deser.LocalDateTimeDeserializer
import
com.github.kittinunf.fuel.core.ResponseDeserializable
import
fr.chenry.android.freshrss.utils.*
import
org.joda.time.DateTimeZone
import
org.joda.time.LocalDateTime
import
org.json.JSONObject
class
ContentItemsDeserializer
:
JsonDeserializer
<
ContentItems
>()
{
override
fun
deserialize
(
p
:
JsonParser
?,
ctxt
:
DeserializationContext
?):
ContentItems
=
p
?.
let
{
_
->
Try
{
JACKSON_OBJECT_MAPPER
.
readTree
<
ArrayNode
>(
p
).
mapNotNull
{
Try
{
JACKSON_OBJECT_MAPPER
.
convertValue
(
it
,
ContentItem
::
class
.
java
)}.
getOrNull
()
}
}.
getOrDefault
(
listOf
())
}
?:
listOf
()
}
class
MicroSecTimestampDeserializer
:
LocalDateTimeDeserializer
()
{
override
fun
deserialize
(
p
:
JsonParser
?,
ctxt
:
DeserializationContext
?):
LocalDateTime
{
val
tz
=
if
(
_format
.
isTimezoneExplicit
)
_format
.
timeZone
else
DateTimeZone
.
forTimeZone
(
ctxt
?.
timeZone
)
return
LocalDateTime
(
p
?.
valueAsString
?.
toLongOrNull
()
?.
div
(
1000
)
?:
0
,
tz
)
}
}
class
MilliSecTimestampDeserializer
:
LocalDateTimeDeserializer
()
{
override
fun
deserialize
(
p
:
JsonParser
?,
ctxt
:
DeserializationContext
?):
LocalDateTime
{
val
tz
=
if
(
_format
.
isTimezoneExplicit
)
_format
.
timeZone
else
DateTimeZone
.
forTimeZone
(
ctxt
?.
timeZone
)
return
LocalDateTime
(
p
?.
valueAsString
?.
toLongOrNull
()
?:
0
,
tz
)
}
}
class
PublishedDateDeserializer
:
LocalDateTimeDeserializer
()
{
override
fun
deserialize
(
p
:
JsonParser
?,
ctxt
:
DeserializationContext
?):
LocalDateTime
{
val
tz
=
if
(
_format
.
isTimezoneExplicit
)
_format
.
timeZone
else
DateTimeZone
.
forTimeZone
(
ctxt
?.
timeZone
)
return
LocalDateTime
((
p
?.
valueAsLong
?:
0L
)
*
1000L
,
tz
)
}
}
object
TagsDeserializer
:
ResponseDeserializable
<
List
<
String
>>
{
override
fun
deserialize
(
content
:
String
):
List
<
String
>
{
return
try
{
val
jsonArray
=
JSONObject
(
content
).
optJSONArray
(
"tags"
)
val
initialCapacity
=
jsonArray
.
length
()
val
tags
=
ArrayList
<
String
>(
initialCapacity
)
for
(
i
in
0
until
initialCapacity
)
tags
.
add
(
jsonArray
.
optJSONObject
(
i
).
optString
(
"id"
))
tags
}
catch
(
e
:
Exception
)
{
this
.
e
(
e
)
listOf
()
}
}
}
object
SubscritpionAddResponseDeserializer
:
ResponseDeserializable
<
String
>
{
override
fun
deserialize
(
content
:
String
):
String
?
{
return
JSONObject
(
content
).
getString
(
"streamId"
)
}
}
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/APIConverter.kt
0 → 100644
View file @
4438b0f6
package
fr.chenry.android.freshrss.store.api.utils
import
okhttp3.ResponseBody
import
retrofit2.Converter
import
retrofit2.Retrofit
import
java.lang.reflect.Type
import
kotlin.reflect.full.primaryConstructor
internal
class
APIConverter
:
Converter
.
Factory
()
{
override
fun
responseBodyConverter
(
type
:
Type
,
annotations
:
Array
<
Annotation
>,
retrofit
:
Retrofit
):
Converter
<
ResponseBody
,
*
>?
{
annotations
.
forEach
{
if
(
it
is
UseDeserializer
)
{
return
it
.
deserializer
.
objectInstance
?:
it
.
deserializer
.
primaryConstructor
?.
call
()
?:
it
.
deserializer
.
constructors
.
find
{
c
->
c
.
parameters
.
isEmpty
()}
?.
call
()
?:
throw
NoSuchMethodException
(
"Unable to find a parameterless constructor for class ${it.deserializer.simpleName}"
)
}
}
return
null
}
}
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/APIInterceptor.kt
0 → 100644
View file @
4438b0f6
package
fr.chenry.android.freshrss.store.api.utils
import
fr.chenry.android.freshrss.store.api.API
import
fr.chenry.android.freshrss.store.api.API.Companion.TOKEN_RENEWAL_FAILED_MESSAGE
import
fr.chenry.android.freshrss.store.api.API.Companion.VOID_ACCOUNT_FAIL_MESSAGE
import
fr.chenry.android.freshrss.store.database.models.Account
import
fr.chenry.android.freshrss.store.database.models.VoidAccount
import
kotlinx.coroutines.runBlocking
import
okhttp3.Interceptor
import
okhttp3.Protocol
import
retrofit2.*
import
kotlin.reflect.full.primaryConstructor
import
okhttp3.Response
as
OkHttp3Response
internal
class
APIInterceptor
(
private
val
account
:
Account
):
Interceptor
{
lateinit
var
service
:
API
override
fun
intercept
(
chain
:
Interceptor
.
Chain
):
OkHttp3Response
{
if
(
account
==
VoidAccount
)
return
OkHttp3Response
.
Builder
()
.
code
(
460
)
.
message
(
VOID_ACCOUNT_FAIL_MESSAGE
)
.
request
(
chain
.
request
())
.
protocol
(
Protocol
.
HTTP_1_1
)
.
build
()
val
request
=
chain
.
request
()
val
invocation
=
request
.
tag
(
Invocation
::
class
.
java
)
!!
val
method
=
invocation
.
method
()
val
builder
=
request
.
newBuilder
()
var
reqUrl
=
request
.
url
()
if
(
method
.
isAnnotationPresent
(
TokenRequired
::
class
.
java
))
{
if
(
account
.
isWriteTokenExpired
)
{
val
response
=
runBlocking
{
service
.
getWriteToken
().
awaitResponse
()}
if
(!
response
.
isSuccessful
)
return
tokenRenewalFailResponse
(
response
)
account
.
writeToken
=
response
.
body
()
!!
.
trim
()
}
builder
.
header
(
"T"
,
account
.
writeToken
)
}
if
(
method
.
isAnnotationPresent
(
JsonOutput
::
class
.
java
))
reqUrl
=
reqUrl
.
newBuilder
().
setQueryParameter
(
"output"
,
"json"
).
build
()
if
(
method
.
isAnnotationPresent
(
AuthorisationRequired
::
class
.
java
))
builder
.
apply
{
header
(
"Authorization"
,
"GoogleLogin auth=${account.SID}"
)
}
method
.
parameterAnnotations
.
forEachIndexed
{
index
,
annotations
->
val
annotation
=
annotations
.
find
{
it
is
QueryTransformer
}
if
(
annotation
!=
null
&&
annotation
is
QueryTransformer
)
{
val
parameter
=
invocation
.
arguments
()[
index
]
!!
val
parameterType
=
method
.
parameterTypes
[
index
]
!!
val
transformer
=
annotation
.
value
.
primaryConstructor
!!
.
call
()
@Suppress
(
"UNCHECKED_CAST"
)
val
queryParams
=
transformer
.
javaClass
.
getMethod
(
"transform"
,
parameterType
)
.
invoke
(
transformer
,
parameter
)
as
List
<
Pair
<
String
,
String
?
>>
queryParams
.
forEach
{
reqUrl
=
if
(
it
.
second
!=
null
)
reqUrl
.
newBuilder
().
setQueryParameter
(
it
.
first
,
it
.
second
).
build
()
else
reqUrl
.
newBuilder
().
removeAllQueryParameters
(
it
.
first
).
build
()
}
}
}
return
builder
.
url
(
reqUrl
).
build
().
let
{
chain
.
proceed
(
it
)}
}
private
fun
<
T
>
tokenRenewalFailResponse
(
response
:
Response
<
T
>)
=
response
.
raw
().
newBuilder
().
message
(
TOKEN_RENEWAL_FAILED_MESSAGE
).
build
()
}
app/src/main/java/fr/chenry/android/freshrss/store/api/utils/Annotations.kt
0 → 100644
View file @
4438b0f6
package
fr.chenry.android.freshrss.store.api.utils
import
fr.chenry.android.freshrss.store.database.models.ReadStatus
import
okhttp3.ResponseBody
import
retrofit2.Converter
import
kotlin.reflect.KClass
/** This annotation will ensure that a valid token is available.
if not, it will perform a query to get a new one */
@Target
(
AnnotationTarget
.
FUNCTION
)
@Retention
(
AnnotationRetention
.
RUNTIME
)
internal
annotation
class
TokenRequired
/** This annotation always applies the correct `Authorization` header to the request */
@Target
(
AnnotationTarget
.
FUNCTION
)
@Retention
(
AnnotationRetention
.
RUNTIME
)