Skip to content
Projects
Groups
Snippets
Help
Sign in / Register
Toggle navigation
Minds Mobile
Project overview
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Locked Files
Issues
174
Merge Requests
13
Security & Compliance
Packages
Wiki
Snippets
Members
Collapse sidebar
Close sidebar
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Minds
Minds Mobile
Compare Revisions
95d7d535966e4760d050836a5655b90065e2dc47...805d46a7b06ed8f43320ba2031d982e8ddc8c599
Source
805d46a7b06ed8f43320ba2031d982e8ddc8c599
...
Target
95d7d535966e4760d050836a5655b90065e2dc47
Compare
Commits (2)
(feat) implement fallback in discovery feeds
· c58a9a56
Martin Santangelo
authored
2 days ago
c58a9a56
Merge branch 'feat/implement-feed-fallback-discovery-feeds' into 'release/3.15.0'
· 805d46a7
Brian Hatchet
authored
37 minutes ago
Implement fallback in discovery feeds See merge request
!479
805d46a7
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
246 additions
and
40 deletions
+246
-40
locales/en.json
View file @
805d46a7
...
...
@@ -294,7 +294,8 @@
"subscribed"
:
"SUBSCRIBED"
,
"boostfeed"
:
"BOOSTFEED"
,
"designYourChannel"
:
"Design your channel"
,
"empty"
:
"Your newsfeed is empty"
"empty"
:
"Your newsfeed is empty"
,
"olderThan"
:
"Older than {{period}}"
},
"comments"
:
{
"successRemoving"
:
"Comment removed succesfully"
,
...
...
This diff is collapsed.
src/common/services/feeds.service.js
View file @
805d46a7
// @flow
import
logService
from
'
./log.service
'
;
import
apiService
from
'
./api.service
'
;
import
{
abort
,
isNetworkFail
}
from
'
../helpers/abortableFetch
'
;
import
{
abort
,
isNetworkFail
}
from
'
../helpers/abortableFetch
'
;
import
entitiesService
from
'
./entities.service
'
;
import
feedsStorage
from
'
./sql/feeds.storage
'
;
import
{
showMessage
}
from
'
react-native-flash-message
'
;
import
{
showMessage
}
from
'
react-native-flash-message
'
;
import
i18n
from
'
./i18n.service
'
;
import
connectivityService
from
'
./connectivity.service
'
;
import
Colors
from
'
../../styles/Colors
'
;
...
...
@@ -15,14 +15,13 @@ export type FeedRecordType = {
owner_guid
:
string
,
timestamp
:
string
,
urn
:
string
,
entity
?:
Object
entity
?:
Object
,
};
/**
* Feed store
*/
export
default
class
FeedsService
{
/**
* @var {boolean}
*/
...
...
@@ -51,7 +50,7 @@ export default class FeedsService {
/**
* @var {Object}
*/
params
:
Object
=
{
sync
:
1
}
params
:
Object
=
{
sync
:
1
}
;
/**
* @var {Array}
...
...
@@ -73,6 +72,16 @@ export default class FeedsService {
*/
paginated
=
true
;
/**
* @var {number|null}
*/
fallbackAt
=
null
;
/**
* @var {number}
*/
fallbackIndex
=
-
1
;
/**
* Get entities from the current page
*/
...
...
@@ -91,9 +100,15 @@ export default class FeedsService {
const
feedPage
=
this
.
feed
.
slice
(
this
.
offset
,
end
);
const
result
:
Array
<
any
>
=
await
entitiesService
.
getFromFeed
(
feedPage
,
this
,
this
.
asActivities
);
const
result
:
Array
<
any
>
=
await
entitiesService
.
getFromFeed
(
feedPage
,
this
,
this
.
asActivities
,
);
if
(
!
this
.
injectBoost
)
return
result
;
if
(
!
this
.
injectBoost
)
{
return
result
;
}
this
.
injectBoosted
(
3
,
result
,
end
);
this
.
injectBoosted
(
8
,
result
,
end
);
...
...
@@ -114,8 +129,10 @@ export default class FeedsService {
*/
injectBoosted
(
position
:
number
,
entities
:
Array
<
BaseModel
>
,
end
:
number
)
{
if
(
this
.
offset
<=
position
&&
end
>=
position
)
{
const
boost
=
boostedContentService
.
fetch
();
if
(
boost
)
entities
.
splice
(
position
+
this
.
offset
,
0
,
boost
);
const
boost
=
boostedContentService
.
fetch
();
if
(
boost
)
{
entities
.
splice
(
position
+
this
.
offset
,
0
,
boost
);
}
}
}
...
...
@@ -127,10 +144,11 @@ export default class FeedsService {
this
.
feed
.
unshift
({
owner_guid
:
entity
.
owner_guid
,
timestamp
:
Date
.
now
().
toString
(),
urn
:
entity
.
urn
urn
:
entity
.
urn
,
});
this
.
offset
++
;
this
.
fallbackIndex
++
;
const
plainEntity
=
entity
.
toPlainObject
();
...
...
@@ -146,6 +164,14 @@ export default class FeedsService {
return
this
.
feed
.
length
>
this
.
limit
+
this
.
offset
;
}
/**
* Set fallback index
* @param {number} value
*/
setFallbackIndex
(
value
:
number
)
{
this
.
fallbackIndex
=
value
;
}
/**
* Set feed
* @param {Array<FeedRecordType>} feed
...
...
@@ -235,6 +261,28 @@ export default class FeedsService {
abort
(
this
);
}
/**
* Calculate the index of the fallback
*/
calculateFallbackIndex
=
()
=>
{
let
index
=
-
1
;
if
(
this
.
fallbackAt
)
{
index
=
this
.
feed
.
findIndex
(
r
=>
r
.
entity
&&
r
.
entity
.
time_created
&&
parseInt
(
r
.
entity
.
time_created
,
10
)
<
this
.
fallbackAt
,
);
}
if
(
index
!==
-
1
)
{
this
.
fallbackIndex
=
index
;
}
else
{
this
.
fallbackIndex
=
-
1
;
}
};
/**
* Fetch
* @param {boolean} more
...
...
@@ -242,17 +290,30 @@ export default class FeedsService {
async
fetch
(
more
:
boolean
=
false
):
Promise
<
void
>
{
abort
(
this
);
const
params
=
{...
this
.
params
,
...{
limit
:
150
,
as_activities
:
this
.
asActivities
?
1
:
0
}};
const
params
=
{
...
this
.
params
,
...{
limit
:
150
,
as_activities
:
this
.
asActivities
?
1
:
0
},
};
if
(
this
.
paginated
&&
more
)
params
.
from_timestamp
=
this
.
pagingToken
;
if
(
this
.
paginated
&&
more
)
{
params
.
from_timestamp
=
this
.
pagingToken
;
}
const
response
=
await
apiService
.
get
(
this
.
endpoint
,
params
,
this
);
if
(
response
.
entities
&&
response
.
entities
.
length
)
{
if
(
more
)
{
this
.
feed
=
this
.
feed
.
concat
(
response
.
entities
);
}
else
{
this
.
feed
=
response
.
entities
;
}
if
(
response
.
fallback_at
)
{
this
.
fallbackAt
=
response
.
fallback_at
;
this
.
calculateFallbackIndex
();
}
else
{
this
.
fallbackAt
=
null
;
this
.
fallbackIndex
=
-
1
;
}
this
.
pagingToken
=
response
[
'
load-next
'
];
}
else
{
this
.
endReached
=
true
;
...
...
@@ -281,6 +342,8 @@ export default class FeedsService {
this
.
pagingToken
=
(
this
.
feed
[
this
.
feed
.
length
-
1
].
timestamp
-
1
).
toString
();
}
else
{
this
.
feed
=
feed
.
feed
;
this
.
fallbackAt
=
feed
.
fallbackAt
;
this
.
fallbackIndex
=
feed
.
fallbackIndex
;
this
.
pagingToken
=
feed
.
next
;
}
return
true
;
...
...
@@ -299,9 +362,13 @@ export default class FeedsService {
const
status
=
await
this
.
fetchLocal
();
try
{
if
(
!
status
)
await
this
.
fetch
();
if
(
!
status
)
{
await
this
.
fetch
();
}
}
catch
(
err
)
{
if
(
err
.
code
===
'
Abort
'
)
return
;
if
(
err
.
code
===
'
Abort
'
)
{
return
;
}
if
(
!
isNetworkFail
(
err
))
{
logService
.
exception
(
'
[FeedService]
'
,
err
);
...
...
@@ -318,13 +385,15 @@ export default class FeedsService {
try
{
await
this
.
fetch
();
}
catch
(
err
)
{
if
(
err
.
code
===
'
Abort
'
)
return
;
if
(
err
.
code
===
'
Abort
'
)
{
return
;
}
if
(
!
isNetworkFail
(
err
))
{
logService
.
exception
(
'
[FeedService]
'
,
err
);
}
if
(
!
await
this
.
fetchLocal
())
{
if
(
!
(
await
this
.
fetchLocal
()))
{
// if there is no local data rethrow the exception
throw
err
;
}
...
...
@@ -332,12 +401,14 @@ export default class FeedsService {
showMessage
({
floating
:
true
,
position
:
'
top
'
,
message
:
(
connectivityService
.
isConnected
?
i18n
.
t
(
'
cantReachServer
'
)
:
i18n
.
t
(
'
noInternet
'
)),
message
:
connectivityService
.
isConnected
?
i18n
.
t
(
'
cantReachServer
'
)
:
i18n
.
t
(
'
noInternet
'
),
description
:
i18n
.
t
(
'
showingStored
'
),
duration
:
1300
,
backgroundColor
:
'
#FFDD63DD
'
,
color
:
Colors
.
dark
,
type
:
"
info
"
,
type
:
'
info
'
,
});
}
}
...
...
@@ -356,8 +427,10 @@ export default class FeedsService {
clear
():
FeedsService
{
this
.
offset
=
0
;
this
.
limit
=
12
;
this
.
fallbackAt
=
null
;
this
.
fallbackIndex
=
-
1
;
this
.
pagingToken
=
''
;
this
.
params
=
{
sync
:
1
};
this
.
params
=
{
sync
:
1
};
this
.
feed
=
[];
return
this
;
}
...
...
This diff is collapsed.
src/common/services/sql/feeds.storage.js
View file @
805d46a7
...
...
@@ -25,8 +25,21 @@ export class FeedsStorage {
try
{
await
this
.
getDb
();
const
params
=
[
key
,
0
,
JSON
.
stringify
({
feed
:
this
.
map
(
feed
.
feed
),
next
:
feed
.
pagingToken
}),
Math
.
floor
(
Date
.
now
()
/
1000
)];
await
this
.
db
.
executeSql
(
'
REPLACE INTO feeds (key, offset, data, updated) values (?,?,?,?)
'
,
params
);
const
params
=
[
key
,
0
,
JSON
.
stringify
({
feed
:
this
.
map
(
feed
.
feed
),
next
:
feed
.
pagingToken
,
fallbackAt
:
feed
.
fallbackAt
,
fallbackIndex
:
feed
.
fallbackIndex
,
}),
Math
.
floor
(
Date
.
now
()
/
1000
),
];
await
this
.
db
.
executeSql
(
'
REPLACE INTO feeds (key, offset, data, updated) values (?,?,?,?)
'
,
params
,
);
}
catch
(
err
)
{
logService
.
exception
(
'
[FeedsStorage]
'
,
err
);
}
...
...
@@ -41,7 +54,10 @@ export class FeedsStorage {
try
{
const
key
=
this
.
getKey
(
feed
);
const
[
result
]
=
await
this
.
db
.
executeSql
(
'
SELECT * FROM feeds WHERE key=? AND offset=?;
'
,
[
key
,
0
]);
const
[
result
]
=
await
this
.
db
.
executeSql
(
'
SELECT * FROM feeds WHERE key=? AND offset=?;
'
,
[
key
,
0
],
);
const
rows
=
result
.
rows
.
raw
();
...
...
This diff is collapsed.
src/common/stores/FeedStore.js
View file @
805d46a7
...
...
@@ -47,7 +47,7 @@ export default class FeedStore {
/**
* Viewed store
*/
viewed
=
new
Viewed
;
viewed
=
new
Viewed
()
;
/**
* Metadata service
...
...
@@ -57,20 +57,27 @@ export default class FeedStore {
/**
* @var {FeedsService}
*/
feedsService
=
new
FeedsService
;
feedsService
=
new
FeedsService
()
;
/**
* The offset of the list
*/
scrollOffset
=
0
;
/**
* Getter fallback index
*/
get
fallbackIndex
()
{
return
this
.
feedsService
.
fallbackIndex
;
}
/**
* Class constructor
* @param {boolean} includeMetadata include a metadata service
*/
constructor
(
includeMetadata
=
false
)
{
if
(
includeMetadata
)
{
this
.
metadataService
=
new
MetadataService
;
this
.
metadataService
=
new
MetadataService
()
;
}
}
...
...
@@ -260,6 +267,15 @@ export default class FeedStore {
return
this
;
}
/**
* Set fallback index
* @param {number} value
*/
setFallbackIndex
(
value
:
number
):
FeedStore
{
this
.
feedsService
.
setFallbackIndex
(
value
);
return
this
;
}
/**
* Fetch from the endpoint
*/
...
...
This diff is collapsed.
src/discovery/DiscoveryFeedScreen.js
View file @
805d46a7
...
...
@@ -31,6 +31,7 @@ import GroupsListItem from '../groups/GroupsListItem'
import
ErrorBoundary
from
'
../common/components/ErrorBoundary
'
;
import
i18n
from
'
../common/services/i18n.service
'
;
import
FeedList
from
'
../common/components/FeedList
'
;
import
FallbackBoundary
from
'
./FallbackBoundary
'
;
/**
* Discovery Feed Screen
...
...
@@ -45,6 +46,34 @@ export default class DiscoveryFeedScreen extends Component {
}
}
/**
* Render activity
*/
renderActivity
=
row
=>
{
let
isLast
=
this
.
props
.
discovery
.
feedStore
.
list
.
entities
.
length
==
row
.
index
+
1
;
const
entity
=
row
.
item
;
const
boundaryText
=
this
.
props
.
discovery
.
feedStore
.
list
.
fallbackIndex
===
row
.
index
?
i18n
.
t
(
'
newsfeed.olderThan
'
,
{
period
:
this
.
props
.
discovery
.
filters
.
period
,
})
:
undefined
;
return
(
<
ErrorBoundary
message
=
{
this
.
cantShowActivity
}
containerStyle
=
{
CS
.
hairLineBottom
}
>
{
boundaryText
&&
<
FallbackBoundary
title
=
{
boundaryText
}
/>
}
<
Activity
entity
=
{
entity
}
newsfeed
=
{
this
.
props
.
feedStore
}
navigation
=
{
this
.
props
.
navigation
}
autoHeight
=
{
false
}
isLast
=
{
isLast
}
/
>
<
/ErrorBoundary
>
)
}
/**
* Render
*/
...
...
@@ -53,6 +82,7 @@ export default class DiscoveryFeedScreen extends Component {
return
(
<
FeedList
renderActivity
=
{
this
.
renderActivity
}
feedStore
=
{
store
}
ListFooterComponent
=
{
this
.
getFooter
}
keyExtractor
=
{
this
.
keyExtractor
}
...
...
This diff is collapsed.
src/discovery/DiscoveryFeedStore.js
View file @
805d46a7
...
...
@@ -29,7 +29,7 @@ class DiscoveryFeedStore {
this
.
buildListStores
();
}
setFeed
(
feed
)
{
setFeed
(
feed
,
fallbackIndex
)
{
this
.
list
.
clear
();
this
.
list
.
viewed
.
clearViewed
();
...
...
@@ -38,6 +38,8 @@ class DiscoveryFeedStore {
.
setFeed
(
feed
)
.
setOffset
(
0
)
.
hydratePage
();
this
.
list
.
setFallbackIndex
(
fallbackIndex
);
}
/**
...
...
This diff is collapsed.
src/discovery/DiscoveryScreen.js
View file @
805d46a7
...
...
@@ -7,7 +7,6 @@ import {
StyleSheet
,
Platform
,
Text
,
FlatList
,
Dimensions
,
RefreshControl
,
View
,
...
...
@@ -18,7 +17,6 @@ import {
import
{
ListItem
,
Avatar
}
from
'
react-native-elements
'
;
import
IonIcon
from
'
react-native-vector-icons/Ionicons
'
;
import
Icon
from
'
react-native-vector-icons/MaterialIcons
'
;
import
Modal
from
'
react-native-modal
'
import
{
observer
,
...
...
@@ -47,14 +45,15 @@ import ErrorBoundary from '../common/components/ErrorBoundary';
import
testID
from
'
../common/helpers/testID
'
;
import
i18n
from
'
../common/services/i18n.service
'
;
import
{
FLAG_VIEW
}
from
'
../common/Permissions
'
;
import
FallbackBoundary
from
'
./FallbackBoundary
'
;
/**
* Discovery screen
*/
export
default
@
inject
(
'
discovery
'
,
'
channel
'
)
@
observer
export
default
class
DiscoveryScreen
extends
Component
{
class
DiscoveryScreen
extends
Component
{
cols
=
3
;
iconSize
=
28
;
...
...
@@ -62,13 +61,13 @@ export default class DiscoveryScreen extends Component {
active
:
false
,
showFeed
:
false
,
itemHeight
:
0
,
q
:
''
}
q
:
''
,
}
;
viewOptsFeed
=
{
viewAreaCoveragePercentThreshold
:
50
,
minimumViewTime
:
300
}
minimumViewTime
:
300
,
}
;
static
navigationOptions
=
{
tabBarIcon
:
({
tintColor
})
=>
(
...
...
@@ -524,14 +523,22 @@ export default class DiscoveryScreen extends Component {
*/
navigateToFeed
=
({
urn
})
=>
{
const
index
=
this
.
props
.
discovery
.
listStore
.
feedsService
.
feed
.
findIndex
(
e
=>
e
.
urn
===
urn
);
let
fallbackIndex
=
this
.
props
.
discovery
.
listStore
.
fallbackIndex
;
this
.
props
.
discovery
.
feedStore
.
setFeed
(
this
.
props
.
discovery
.
listStore
.
feedsService
.
feed
.
slice
(
index
));
if
(
fallbackIndex
!==
-
1
&&
fallbackIndex
>
index
)
{
fallbackIndex
-=
index
;
}
this
.
props
.
discovery
.
feedStore
.
setFeed
(
this
.
props
.
discovery
.
listStore
.
feedsService
.
feed
.
slice
(
index
),
fallbackIndex
,
);
this
.
props
.
navigation
.
push
(
'
DiscoveryFeed
'
,
{
'
showFeed
'
:
index
,
title
:
_
.
capitalize
(
this
.
props
.
discovery
.
filters
.
filter
)
+
'
'
+
_
.
capitalize
(
this
.
props
.
discovery
.
filters
.
type
)
})
}
})
;
}
;
/**
* Render a tile
...
...
@@ -540,12 +547,20 @@ export default class DiscoveryScreen extends Component {
if
(
!
this
.
state
.
active
&&
row
.
item
.
isGif
())
{
return
<
View
style
=
{{
height
:
this
.
state
.
itemHeight
,
width
:
this
.
state
.
itemHeight
}}
/>
;
}
const
boundaryText
=
this
.
props
.
discovery
.
listStore
.
fallbackIndex
===
row
.
index
?
i18n
.
t
(
'
newsfeed.olderThan
'
,
{
period
:
this
.
props
.
discovery
.
filters
.
period
,
})
:
undefined
;
return
(
<
ErrorBoundary
message
=
{
this
.
tileError
}
containerStyle
=
{[
CS
.
centered
,
{
width
:
this
.
state
.
itemHeight
,
height
:
this
.
state
.
itemHeight
}]}
textSmall
=
{
true
}
>
<
DiscoveryTile
entity
=
{
row
.
item
}
size
=
{
this
.
state
.
itemHeight
}
onPress
=
{
this
.
navigateToFeed
}
boundaryText
=
{
boundaryText
}
/
>
<
/ErrorBoundary
>
);
...
...
@@ -567,8 +582,16 @@ export default class DiscoveryScreen extends Component {
* Render activity item
*/
renderActivity
=
(
row
)
=>
{
const
boundaryText
=
this
.
props
.
discovery
.
listStore
.
fallbackIndex
===
row
.
index
?
i18n
.
t
(
'
newsfeed.olderThan
'
,
{
period
:
this
.
props
.
discovery
.
filters
.
period
,
})
:
undefined
;
return
(
<
ErrorBoundary
containerStyle
=
{
CS
.
hairLineBottom
}
>
{
boundaryText
&&
<
FallbackBoundary
title
=
{
boundaryText
}
/>
}
<
Activity
entity
=
{
row
.
item
}
navigation
=
{
this
.
props
.
navigation
}
autoHeight
=
{
false
}
/
>
<
/ErrorBoundary
>
);
...
...
This diff is collapsed.
src/discovery/DiscoveryStore.js
View file @
805d46a7
...
...
@@ -75,14 +75,14 @@ class DiscoveryStore {
if
(
this
.
filters
.
type
!==
'
lastchannels
'
)
{
this
.
fetch
();
}
}
}
;
/**
* On search change
*/
onSearchChange
=
(
searchtext
)
=>
{
this
.
fetch
(
true
);
}
}
;
fetch
(
refresh
=
false
)
{
const
hashtags
=
appStores
.
hashtag
.
hashtag
?
encodeURIComponent
(
appStores
.
hashtag
.
hashtag
)
:
''
;
...
...
@@ -99,6 +99,7 @@ class DiscoveryStore {
all
,
query
:
this
.
filters
.
searchtext
,
nsfw
:
this
.
filters
.
nsfw
.
concat
([]),
period_fallback
:
1
,
})
.
fetchRemoteOrLocal
(
refresh
);
}
...
...
This diff is collapsed.
src/discovery/DiscoveryTile.js
View file @
805d46a7
...
...
@@ -104,11 +104,24 @@ class DiscoveryTile extends Component {
<
ExplicitOverlay
entity
=
{
entity
}
iconSize
=
{
45
}
hideText
=
{
true
}
/
>
)
:
null
;
const
boundary
=
this
.
props
.
boundaryText
?
(
<
View
style
=
{[
CS
.
positionAbsoluteTop
,
CS
.
backgroundGreyed
,
CS
.
centered
,
styles
.
boundary
,
]}
>
<
Text
>
{
this
.
props
.
boundaryText
}
<
/Text
>
<
/View
>
)
:
null
;
return
(
<
TouchableOpacity
onPress
=
{
this
.
_onPress
}
style
=
{[
this
.
state
.
style
,
styles
.
tile
]}
>
<
View
style
=
{[
CS
.
flexContainer
,
CS
.
backgroundGreyed
]}
>
{
boundary
}
<
FastImage
source
=
{
url
}
style
=
{
CS
.
positionAbsolute
}
...
...
@@ -123,6 +136,11 @@ class DiscoveryTile extends Component {
}
const
styles
=
StyleSheet
.
create
({
boundary
:
{
height
:
20
,
width
:
'
100%
'
,
zIndex
:
1000
,
},
tile
:
{
paddingTop
:
1
,
paddingBottom
:
1
,
...
...
This diff is collapsed.
src/discovery/FallbackBoundary.js
0 → 100644
View file @
805d46a7
import
React
from
'
react
'
;
import
{
View
,
Text
}
from
'
react-native
'
;
import
{
CommonStyle
as
CS
}
from
'
../styles/Common
'
;
/**
* Fallback boundary for feed views
* @param {object} props
*/
export
default
function
FallbackBoundary
(
props
)
{
return
(
<
View
style
=
{[
CS
.
flexContainer
,
CS
.
centered
,
CS
.
paddingTop
,
CS
.
paddingBottom
,
CS
.
borderBottomHair
,
CS
.
fullWidth
,
CS
.
borderGreyed
,
CS
.
backgroundLight
,
]}
>
<
Text
style
=
{[
CS
.
colorDarkGreyed
,
CS
.
fontXL
]}
>
{
props
.
title
}
<
/Text
>
<
/View
>
);
}
This diff is collapsed.