Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: check buckets when closing browser #31

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/__mocks__/state.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

210 changes: 2 additions & 208 deletions app/components/AppStateListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,21 @@ import React, { useEffect } from 'react'
import {
AppState
} from 'react-native'
import Clipboard from "@react-native-community/clipboard"
import SharedGroupPreferences from 'react-native-shared-group-preferences'
import { parseString } from 'react-native-xml2js'

import { isIgnoredUrl, addIgnoredUrl } from '../storage/async-storage'
import log from '../utils/log'
import DarkModeListener from './DarkModeListener'

class AppStateListener extends React.Component {

group = 'group.com.adam-butler.rizzle'

MINIMUM_UPDATE_INTERVAL = 600000 // 10 minutes

constructor (props) {
super(props)
this.props = props

this.checkClipboard = this.checkClipboard.bind(this)
this.checkPageBucket = this.checkPageBucket.bind(this)
this.checkFeedBucket = this.checkFeedBucket.bind(this)
this.handleAppStateChange = this.handleAppStateChange.bind(this)
this.showSavePageModal = this.showSavePageModal.bind(this)
this.showSaveFeedModal = this.showSaveFeedModal.bind(this)

AppState.addEventListener('change', this.handleAppStateChange)

this.checkBuckets()
}

async checkBuckets () {
await this.checkClipboard()
await this.checkPageBucket()
await this.checkFeedBucket()
// see Rizzle component
this.props.checkBuckets()
}

async handleAppStateChange (nextAppState) {
Expand All @@ -47,7 +27,7 @@ class AppStateListener extends React.Component {
this.setState({
doNothing: Date.now()
})
await this.checkBuckets()
this.props.checkBuckets()

if (!global.isStarting && (Date.now() - this.props.lastUpdated > this.MINIMUM_UPDATE_INTERVAL)) {
this.props.fetchData()
Expand All @@ -59,192 +39,6 @@ class AppStateListener extends React.Component {
}
}

async checkClipboard () {
console.log('Checking clipboard')
try {
const hasUrl = await Clipboard.hasURL()
if (!hasUrl) {
return
}
let contents = await Clipboard.getString() ?? ''
// TODO make this more robust
// right now we're ignoring any URLs that include 'rizzle.net'
// this is due to links getting into clipboard during the email auth process
if (contents.substring(0, 4) === 'http' &&
contents.indexOf('rizzle.net') === -1) {
const isIgnored = await isIgnoredUrl(contents)
if (!isIgnored) {
this.showSavePageModal.call(this, contents, true)
}
} else if (contents.substring(0, 6) === '<opml>') {
}
} catch(err) {
log('checkClipboard', err)
}
}

async checkPageBucket () {
SharedGroupPreferences.getItem('page', this.group).then(value => {
console.log('CHECKING PAGE BUCKET: ' + value)
if (value !== null) {
SharedGroupPreferences.setItem('page', null, this.group)
const parsed = JSON.parse(value)
const pages = typeof parsed === 'object' ?
parsed :
[parsed]
console.log(`Got ${pages.length} page${pages.length === 1 ? '' : 's'} to save`)
const that = this
pages.forEach(page => {
// ugh, need a timeout to allow for rehydration
setTimeout(() => {
that.savePage.call(that, page)
}, 100)
})
}
}).catch(err => {
// '1' just means that there is nothing in the bucket
if (err !== 1) {
log('checkPageBucket', err)
}
})
}

async checkFeedBucket () {
SharedGroupPreferences.getItem('feed', this.group).then(value => {
if (value !== null) {
const url = value
const that = this
SharedGroupPreferences.setItem('feed', null, this.group)
console.log(`Got a feed to subscribe to: ${url}`)
// TODO check that value is a feed url
// TODO check that feed is not already subscribed!
// right now it will just get ignored if it's already subscribed
// but it might be nice to say that in the message
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText)
}
return response
})
.then((response) => {
return response.text()
})
.then((xml) => {
parseString(xml, (error, result) => {
if (error) {
throw error
}
let title, description
if (result.rss) {
title = typeof result.rss.channel[0].title[0] === 'string' ?
result.rss.channel[0].title[0] :
result.rss.channel[0].title[0]._
description = result.rss.channel[0].description ?
(typeof result.rss.channel[0].description[0] === 'string' ?
result.rss.channel[0].description[0] :
result.rss.channel[0].description[0]._) :
''
} else if (result.feed) {
// atom
title = typeof result.feed.title[0] === 'string' ?
result.feed.title[0] :
result.feed.title[0]._
description = result.feed.subtitle ?
(typeof result.feed.subtitle[0] === 'string' ?
result.feed.subtitle[0] :
result.feed.subtitle[0]._) :
''
}
this.showSaveFeedModal(url, title, description, that)
})
})
.catch(err => {
log('checkFeedBucket', err)
})
}
}).catch(err => {
// '1' just means that there is nothing in the bucket
if (err !== 1) {
log('checkFeedBucket', err)
}
})
}

savePage (page) {
console.log(`Saving page: ${page.url}`)
this.props.saveURL(page.url, page.title)
this.props.addMessage('Saved page: ' + (page.title ?? page.url))
}

addFeed (feed) {
this.props.addFeed(feed)
this.props.addMessage('Added feed: ' + (feed.title ?? feed.url))
this.props.fetchData()
}

showSavePageModal (url, isClipboard = false) {
let displayUrl = url
if (displayUrl.length > 64) {
displayUrl = displayUrl.slice(0, 64) + '…'
}
let modalText = [
{
text: 'Save this page?',
style: ['title']
},
{
text: displayUrl,
style: ['em']
}
]
if (isClipboard) {
modalText.push({
text: 'This URL was in your clipboard. Copying a URL is an easy way to save a page in Rizzle.',
style: ['hint']
})
}
const onOk = () => {
this.savePage({ url })
}
// onOk = onOk.bind(this)
this.props.showModal({
modalText,
modalHideCancel: false,
modalShow: true,
modalOnOk: onOk.bind(this)
})
}

showSaveFeedModal (url, title, description, scope) {
scope.props.showModal({
modalText: [
{
text: 'Add this feed?',
style: ['title']
},
{
text: title,
style: ['em']
},
{
text: description,
style: ['em', 'smaller']
}
],
modalHideCancel: false,
modalShow: true,
modalOnOk: () => {
scope.props.addFeed({
url,
title,
description
})
scope.props.fetchData()
}
})
}

render () {
return <DarkModeListener />
}
Expand Down
47 changes: 3 additions & 44 deletions app/containers/AppStateListener.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
import {
SAVE_EXTERNAL_URL,
ItemType
} from '../store/items/types'
import {
STATE_ACTIVE,
STATE_INACTIVE
} from '../store/config/types'
import { connect } from 'react-redux'
import { ADD_FEED } from '../store/feeds/types'
import {
CHECK_BUCKETS,
ADD_MESSAGE,
FETCH_ITEMS,
SHOW_MODAL
} from '../store/ui/types'
import {
SET_DISPLAY_MODE
} from '../store/items/types'
import AppStateListener from '../components/AppStateListener'

const mapStateToProps = (state) => {
Expand All @@ -29,49 +21,16 @@ const mapStateToProps = (state) => {

const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch({
type: FETCH_ITEMS
checkBuckets: () => dispatch({
type: CHECK_BUCKETS
}),
updateCurrentAppState: (state) => dispatch(updateCurrentAppState(state)),
saveURL: (url, title) => {
dispatch({
type: SAVE_EXTERNAL_URL,
url,
title
})
dispatch({
type: SET_DISPLAY_MODE,
displayMode: ItemType.saved
})
},
addFeed: (feed) => dispatch({
type: ADD_FEED,
feed
}),
showModal: (modalProps) => {
console.log("SHOW MODAL!")
dispatch({
type: SHOW_MODAL,
modalProps
})
},
appWentInactive: () => dispatch({
type: STATE_INACTIVE
}),
appWentActive: () => dispatch({
type: STATE_ACTIVE
}),
setDarkMode: (isDarkMode) => dispatch({
type: SET_DARK_MODE,
isDarkMode
}),
addMessage: (messageString) => dispatch({
type: ADD_MESSAGE,
message: {
messageString,
isSelfDestruct: true
}
})
}
}

Expand Down
1 change: 1 addition & 0 deletions app/docs/todo.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

- check bucket when closing browser
- why do saved external items (sometimes) render twice?
- onboarding in dark mode
- muting a feed on non-rizzle accounts
Expand Down
Loading