...
 
Commits (233)
......@@ -55,6 +55,9 @@ jobs:
steps:
- checkout
- run:
name: Install sentry cli for fastlane plugin
command: brew install getsentry/tools/sentry-cli
- run:
name: set Ruby version
command: echo "ruby-2.4" > ~/.ruby-version
......@@ -129,6 +132,15 @@ jobs:
only:
- /release-*/
- run:
name: Prepare sentry release
command: fastlane preparesentry
working_directory: ios
- persist_to_workspace:
root: ~/mobile-native/ios
paths:
- version
- run:
name: Upload to Testflight release
command: fastlane testflight
......@@ -138,7 +150,27 @@ jobs:
- /stable-*/
- /release-*/
- test/circle-ci
sentry:
docker:
- image: getsentry/sentry-cli
working_directory: ~/mobile-native
steps:
- attach_workspace:
at: /tmp/workspace
- run:
name: Install git
command: |
apk add git
- checkout
- run:
name: Tag sentry release
command: |
version=`cat /tmp/workspace/version`
echo Tagging release with ${version}
ls -a
# release created by fastlane preparesentry
sentry-cli releases set-commits --commit "Minds / Minds Mobile@${CIRCLE_SHA1}" ${version} --log-level=debug
sentry-cli releases finalize ${version}
workflows:
version: 2
node-ios:
......@@ -147,3 +179,6 @@ workflows:
- ios:
requires:
- node
- sentry:
requires:
- ios
module.exports = {
"parser": "babel-eslint",
"plugins": [
"react",
"react-native",
"flowtype"
],
"extends": ["plugin:react-native/all"],
"extends": ["plugin:react-native/all", "plugin:react/recommended"],
"env": {
"react-native/react-native": true
},
"rules": {
"no-unused-vars": [1],
"react/jsx-uses-vars": [2],
"flowtype/boolean-style": [
2,
"boolean"
......
......@@ -31,6 +31,7 @@ emoji=true
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
esproposal.decorators=ignore
module.system=haste
module.system.haste.use_name_reducers=true
......
#!/bin/sh
if git commit -v --dry-run | grep '!testcode' >/dev/null 2>&1
then
echo "Trying to commit test code."
exit 1
else
exit 0
fi
\ No newline at end of file
......@@ -63,4 +63,6 @@ buck-out/
coverage/
# Sentry secrets
sentry.properties
\ No newline at end of file
sentry.properties
!/.githooks
\ No newline at end of file
......@@ -62,11 +62,7 @@ import feedsStorage from './src/common/services/sql/feeds.storage';
import connectivityService from './src/common/services/connectivity.service';
import sqliteStorageProviderService from './src/common/services/sqlite-storage-provider.service';
import commentStorageService from './src/comments/CommentStorageService';
import { Sentry } from 'react-native-sentry';
Sentry.config('https://d650fc58f2da4dc8ae9d95847bce152d@sentry.io/1538735').install();
import * as Sentry from '@sentry/react-native';
let deepLinkUrl = '';
......@@ -83,8 +79,8 @@ sessionService.onLogin(async () => {
const user = sessionService.getUser();
Sentry.setUserContext({
userID: user.guid
Sentry.configureScope(scope => {
scope.setUser({id: user.guid});
});
logService.info('[App] Getting minds settings and onboarding progress');
......
......@@ -9,13 +9,54 @@ import {
import { onError } from "mobx-react";
import logService from './src/common/services/log.service';
import Sentry from 'react-native-sentry';
import * as Sentry from '@sentry/react-native';
import { isAbort, isNetworkFail } from './src/common/helpers/abortableFetch';
import { isApiError } from './src/common/services/api.service';
// Init Sentry (if not running test)
if (process.env.JEST_WORKER_ID === undefined) {
Sentry.init({
dsn: 'https://d650fc58f2da4dc8ae9d95847bce152d@sentry.io/1538735',
ignoreErrors: [
'Non-Error exception captured with keys: code, domain, localizedDescription', // ignore initial error of sdk
],
beforeSend(event, hint) {
if (hint.originalException) {
// ignore network request failed
if (isNetworkFail(hint.originalException)) {
return null;
}
// ignore aborts
if (isAbort(hint.originalException)) {
return null;
}
// only log api 500 errors
if (isApiError(hint.originalException) && hint.originalException.status < 500) {
return null;
}
}
// for dev only log into the console
if (__DEV__) {
console.log('sentry', event, hint);
return null;
}
return event;
}
});
}
// Log Mobx global errors
onError(error => {
console.log(error);
logService.exception(error);
})
// react-native-exception-handler global handlers
if (!__DEV__) {
/**
* Globar error handlers
......
......@@ -19,8 +19,6 @@ import keychain from './src/keychain/KeychainStore';
import blockchainTransaction from './src/blockchain/transaction-modal/BlockchainTransactionStore';
import blockchainWallet from './src/blockchain/wallet/BlockchainWalletStore';
import blockchainWalletSelector from './src/blockchain/wallet/BlockchainWalletSelectorStore';
import payments from './src/payments/PaymentsStore';
import checkoutModal from './src/payments/checkout/CheckoutModalStore';
import capture from './src/capture/CaptureStore';
import withdraw from './src/wallet/tokens/WithdrawStore';
import hashtag from './src/common/stores/HashtagStore';
......@@ -55,8 +53,6 @@ const stores = {
blockchainWallet: new blockchainWallet(),
blockchainWalletSelector: new blockchainWalletSelector(),
channelSubscribersStore: new channelSubscribersStore(),
payments: new payments(),
checkoutModal: new checkoutModal(),
capture: new capture(),
withdraw: new withdraw(),
hashtag: new hashtag(),
......
......@@ -14,6 +14,7 @@ function load(count) {
edited:"",
guid:code,
mature:false,
time_created: "1522036284",
ownerObj:{
guid: "824853017709780997",
type: "user",
......@@ -36,6 +37,14 @@ function load(count) {
wire_totals: {
tokens: 1000000000000000000
},
_list: {
viewed: {
viewed: new Map([["1019155171608096768",true]]),
addViewed: () => {
return;
}
}
},
getThumbSource: () => {
return {
source:'http://thisisaurl'
......
......@@ -18,6 +18,7 @@ jest.mock('../../../src/newsfeed/NewsfeedService');
import { getSingle } from '../../../src/newsfeed/NewsfeedService';
import entitiesService from '../../../src/common/services/entities.service';
jest.mock('../../../src/common/BaseModel');
jest.mock('../../../src/newsfeed/activity/Activity', () => 'Activity');
jest.mock('../../../src/comments/CommentList', () => 'CommentList');
jest.mock('../../../src/common/components/CenteredLoading', () => 'CenteredLoading');
......
......@@ -17,7 +17,14 @@ exports[`Activity component renders correctly 1`] = `
<Pinned
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -63,6 +70,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -74,7 +82,14 @@ exports[`Activity component renders correctly 1`] = `
<OwnerBlock
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -120,6 +135,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -147,7 +163,14 @@ exports[`Activity component renders correctly 1`] = `
<ActivityActionSheet
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -193,6 +216,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -234,7 +258,7 @@ exports[`Activity component renders correctly 1`] = `
]
}
>
Invalid date
Mar 25, 2018, 20:51
</Text>
</TouchableOpacity>
</OwnerBlock>
......@@ -248,7 +272,14 @@ exports[`Activity component renders correctly 1`] = `
<ExplicitText
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -294,6 +325,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -316,7 +348,14 @@ exports[`Activity component renders correctly 1`] = `
<Translate
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -362,6 +401,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -380,7 +420,14 @@ exports[`Activity component renders correctly 1`] = `
<MediaView
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -426,6 +473,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -450,7 +498,14 @@ exports[`Activity component renders correctly 1`] = `
<Actions
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -496,6 +551,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -513,7 +569,14 @@ exports[`Activity component renders correctly 1`] = `
<ActivityMetrics
entity={
ActivityModel {
"__list": null,
"__list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"allow_comments": true,
"attachment_guid": false,
"blurb": false,
......@@ -559,6 +622,7 @@ exports[`Activity component renders correctly 1`] = `
"thumbs:down:user_guids": undefined,
"thumbs:up:count": undefined,
"thumbs:up:user_guids": undefined,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......
......@@ -70,6 +70,8 @@ exports[`Activity editor component renders correctly 1`] = `
nsfwValue={Array []}
onLocking={[Function]}
onNsfw={[Function]}
onScheduled={[Function]}
timeCreatedValue={2018-03-26T03:51:24.000Z}
/>
</View>
<View
......
......@@ -18,6 +18,14 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
<CommentList
entity={
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Object {
"1019155171608096768": true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -42,6 +50,7 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -54,6 +63,14 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
autoHeight={false}
entity={
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Object {
"1019155171608096768": true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -78,6 +95,7 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -91,6 +109,14 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
"state": Object {
"params": Object {
"entity": Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -115,6 +141,7 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -134,6 +161,14 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
"state": Object {
"params": Object {
"entity": Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -158,6 +193,7 @@ exports[`Activity screen component renders correctly with an entity as param 2`]
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......
......@@ -13,7 +13,7 @@ exports[`ForgotPassword component should renders correctly 1`] = `
style={
Array [
Object {
"color": "white",
"color": "#FFFFFF",
},
Object {
"fontSize": 14,
......
......@@ -77,7 +77,7 @@ exports[`ForgotScreen component should renders correctly 1`] = `
style={
Array [
Object {
"color": "white",
"color": "#FFFFFF",
},
Object {
"fontSize": 14,
......
......@@ -342,7 +342,7 @@ exports[`LoginForm component should renders correctly 1`] = `
style={
Array [
Object {
"color": "white",
"color": "#FFFFFF",
},
Object {
"fontWeight": "bold",
......@@ -396,7 +396,7 @@ exports[`LoginForm component should renders correctly 1`] = `
style={
Array [
Object {
"color": "white",
"color": "#FFFFFF",
},
]
}
......@@ -506,7 +506,7 @@ exports[`LoginForm component should renders correctly 1`] = `
"padding": 10,
},
Object {
"color": "white",
"color": "#FFFFFF",
},
]
}
......@@ -634,7 +634,7 @@ exports[`LoginForm component should renders correctly 1`] = `
"alignItems": "center",
"backgroundColor": "white",
"borderColor": "#777777",
"borderRadius": 15,
"borderRadius": 20,
"borderWidth": 1,
"margin": 4,
"opacity": 1,
......@@ -671,7 +671,7 @@ exports[`LoginForm component should renders correctly 1`] = `
"alignItems": "center",
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 15,
"borderRadius": 20,
"borderWidth": 1,
"margin": 4,
"opacity": 1,
......
......@@ -331,7 +331,8 @@ describe('cature poster component', () => {
// should be called only once
expect(capture.post.mock.calls.length).toBe(1);
expect(capture.post.mock.calls[0][0]).toEqual({ nsfw: [], message: "some awesome post", wire_threshold: null});
const entity = capture.post.mock.calls[0][0];
expect(capture.post.mock.calls[0][0]).toEqual({ nsfw: [], message: "some awesome post", wire_threshold: null, "time_created": entity.time_created});
expect(result).toEqual(response)
......@@ -400,12 +401,14 @@ describe('cature poster component', () => {
// should be called only once
expect(capture.post.mock.calls.length).toBe(1);
const entity = capture.post.mock.calls[0][0];
expect(capture.post.mock.calls[0][0]).toEqual({
nsfw: [],
message: "some awesome post",
wire_threshold: null,
facebook: 1,
twitter: 1
twitter: 1,
time_created: entity.time_created
});
done();
......@@ -446,14 +449,15 @@ describe('cature poster component', () => {
// should be called only once
expect(capture.post).toBeCalled();
const entity = capture.post.mock.calls[0][0];
// should send the attachment data
console.log(capture.post.mock.calls);
expect(capture.post.mock.calls[0][0]).toEqual({
nsfw: [],
message: "some awesome post",
wire_threshold: null,
attachment_guid: 1000,
attachment_license: ''}
attachment_license: '',
time_created: entity.time_created}
);
// should return server response
expect(result).toEqual(response)
......
......@@ -16,6 +16,16 @@ jest.mock('../../src/capture/CaptureStore');
jest.mock('../../src/common/components/LicensePicker', () => 'LicensePicker');
jest.mock('../../src/newsfeed/topbar/TagsSubBar', () => 'TagsSubBar');
Date = class extends Date {
constructor(date) {
if (date) {
return super(date);
}
return new Date('2018-09-20T23:00:00Z');
}
}
defaultState = {
mature: false,
......@@ -53,6 +63,7 @@ const testRenderWithValue = (value) => {
onMature={fn}
onShare={fn}
onLocking={fn}
onScheduled={fn}
/>
).toJSON();
expect(preview).toMatchSnapshot();
......@@ -104,7 +115,7 @@ describe('cature poster flags component', () => {
const hashtagStore = new HashtagStore();
store.loadSuggestedTags.mockResolvedValue();
const capturePosterFlag = renderer.create(
let capturePosterFlag = renderer.create(
<CapturePosterFlags
capture={store}
hashtag={hashtagStore}
......@@ -125,6 +136,19 @@ describe('cature poster flags component', () => {
store.attachment.hasAttachment = true;
capturePosterFlag = renderer.create(
<CapturePosterFlags
capture={store}
hashtag={hashtagStore}
matureValue={defaultState.mature}
shareValue={defaultState.share}
lockValue={defaultState.lock}
onMature={fn}
onShare={fn}
onLocking={fn}
/>
);
picker = capturePosterFlag.root.findAllByType('LicensePicker');
// check there is 1 license picker
......
......@@ -3,18 +3,19 @@
exports[`channel actions component should renders correctly 1`] = `
<View
style={
Object {
"alignItems": "center",
"backgroundColor": "#FFF",
"flexDirection": "row",
"height": 40,
"justifyContent": "flex-end",
"paddingLeft": 8,
"width": 40,
}
Array [
Object {
"flexDirection": "row",
"justifyContent": "flex-end",
},
Object {
"marginTop": 10,
},
]
}
>
<View
accessibilityLabel="Wire Button"
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
......@@ -25,12 +26,16 @@ exports[`channel actions component should renders correctly 1`] = `
onStartShouldSetResponder={[Function]}
style={
Object {
"alignContent": "center",
"alignItems": "center",
"alignSelf": "center",
"flex": 1,
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"justifyContent": "center",
"margin": 4,
"opacity": 1,
"padding": 4,
}
}
>
......@@ -39,10 +44,17 @@ exports[`channel actions component should renders correctly 1`] = `
style={
Array [
Object {
"color": "rgb(70, 144, 214)",
"fontSize": 40,
"color": undefined,
"fontSize": 18,
},
undefined,
Array [
Object {
"marginLeft": 5,
},
Object {
"color": "#4690D6",
},
],
Object {
"fontFamily": "Ionicons",
"fontStyle": "normal",
......@@ -54,30 +66,74 @@ exports[`channel actions component should renders correctly 1`] = `
>
</Text>
<Text
style={
Array [
Object {
"color": "#4690D6",
},
Array [
Object {
"marginLeft": 5,
},
Object {
"marginRight": 5,
},
],
]
}
>
Wire
</Text>
</View>
<Text
allowFontScaling={false}
onPress={[Function]}
<View
accessibilityLabel="More"
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Array [
Object {
"color": undefined,
"fontSize": 24,
},
Object {
"color": "#888888",
"paddingLeft": 10,
},
Object {
"fontFamily": "Ionicons",
"fontStyle": "normal",
"fontWeight": "normal",
},
Object {},
]
Object {
"alignItems": "center",
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"justifyContent": "center",
"margin": 4,
"opacity": 1,
"padding": 4,
}
}
>
</Text>
<Text
style={
Array [
Object {
"color": "#4690D6",
},
Array [
Object {
"marginLeft": 5,
},
Object {
"marginRight": 5,
},
],
]
}
>
More
</Text>
</View>
</View>
`;
......@@ -19,6 +19,14 @@ exports[`Channel screen component should renders correctly 1`] = `
"state": Object {
"params": Object {
"entity": Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -44,6 +52,7 @@ exports[`Channel screen component should renders correctly 1`] = `
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -134,9 +143,9 @@ exports[`Channel screen component should renders correctly 1`] = `
},
"buttonscol": Object {
"alignItems": "center",
"alignSelf": "flex-end",
"alignSelf": "flex-start",
"flexDirection": "row",
"justifyContent": "flex-end",
"justifyContent": "flex-start",
},
"carouselcontainer": Object {
"flex": 1,
......@@ -187,7 +196,7 @@ exports[`Channel screen component should renders correctly 1`] = `
"name": Object {
"color": "#444",
"fontFamily": "Roboto",
"fontSize": 20,
"fontSize": 22,
"fontWeight": "700",
"letterSpacing": 0.5,
"marginRight": 8,
......@@ -224,7 +233,7 @@ exports[`Channel screen component should renders correctly 1`] = `
},
"username": Object {
"color": "#999",
"fontSize": 10,
"fontSize": 14,
},
"wrappedAvatar": Object {
"borderRadius": 55,
......@@ -273,6 +282,14 @@ exports[`Channel screen component should renders correctly 1`] = `
"state": Object {
"params": Object {
"entity": Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -298,6 +315,7 @@ exports[`Channel screen component should renders correctly 1`] = `
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -317,6 +335,14 @@ exports[`Channel screen component should renders correctly 1`] = `
"state": Object {
"params": Object {
"entity": Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -342,6 +368,7 @@ exports[`Channel screen component should renders correctly 1`] = `
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......
......@@ -180,74 +180,60 @@ exports[`channel header component owner should render correctly 1`] = `
someusermane
</Text>
</View>
<View>
<TouchableHighlight
accessibilityLabel="Subscribe to this channel"
onPress={[Function]}
style={
Array [
Object {
"borderColor": "#ececec",
"borderRadius": 30,
"borderWidth": 1,
"padding": 8,
},
Object {
"borderColor": "#4690D6",
},
undefined,
]
}
underlayColor="transparent"
>
<Text
style={
Object {
"color": "#4690D6",
}
}
>
SUBSCRIBE
</Text>
</TouchableHighlight>
</View>
<View>
<View
style={
Array [
Object {
"flexDirection": "row",
"justifyContent": "flex-end",
},
Object {
"marginTop": 10,
},
]
}
>
<View
accessibilityLabel="Subscribe to this channel"
accessible={true}
isTVSelectable={true}
onResponderGrant={[Function]}
onResponderMove={[Function]}
onResponderRelease={[Function]}
onResponderTerminate={[Function]}
onResponderTerminationRequest={[Function]}
onStartShouldSetResponder={[Function]}
style={
Object {
"alignItems": "center",
"backgroundColor": "#FFF",
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"height": 40,
"justifyContent": "flex-end",
"paddingLeft": 8,
"width": 40,
"justifyContent": "center",
"margin": 4,
"marginLeft": 0,
"opacity": 1,
"padding": 4,
}
}
>
<Text
allowFontScaling={false}
onPress={[Function]}
style={
Array [
Object {
"color": undefined,
"fontSize": 24,
},
Object {
"color": "#888888",
"paddingLeft": 10,
},
Object {
"fontFamily": "Ionicons",
"fontStyle": "normal",
"fontWeight": "normal",
"color": "#4690D6",
},
Object {},
undefined,
]
}
>
SUBSCRIBE
</Text>
</View>
</View>
......
......@@ -15,7 +15,7 @@ exports[`renders correctly 1`] = `
"alignItems": "center",
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 15,
"borderRadius": 20,
"borderWidth": 1,
"margin": 4,
"opacity": 1,
......
......@@ -51,7 +51,7 @@ exports[`Explicit overlay component renders correctly 1`] = `
style={
Array [
Object {
"color": "white",
"color": "#FFFFFF",
},
Object {
"elevation": 4,
......
......@@ -22,6 +22,14 @@ exports[`Media view component renders correctly 1`] = `
<ExplicitImage
entity={
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -66,6 +74,7 @@ exports[`Media view component renders correctly 1`] = `
"shouldBeBlured": [MockFunction],
"subtype": "image",
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......
......@@ -46,6 +46,14 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
data={
Array [
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid0",
......@@ -70,6 +78,7 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
"rowKey": "something0",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -77,6 +86,14 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
},
},
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid1",
......@@ -101,6 +118,7 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
"rowKey": "something1",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -108,6 +126,14 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
},
},
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid2",
......@@ -132,6 +158,7 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
"rowKey": "something2",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -139,6 +166,14 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
},
},
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid3",
......@@ -163,6 +198,7 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
"rowKey": "something3",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......@@ -170,6 +206,14 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
},
},
Object {
"_list": Object {
"viewed": Object {
"addViewed": [Function],
"viewed": Map {
"1019155171608096768" => true,
},
},
},
"attachment_guid": false,
"blurb": false,
"container_guid": "activityguid4",
......@@ -194,6 +238,7 @@ exports[`newsfeed list screen component should renders correctly 1`] = `
"rowKey": "something4",
"shouldBeBlured": [MockFunction],
"thumbnail_src": false,
"time_created": "1522036284",
"title": "TITLE",
"type": "activity",
"wire_totals": Object {
......
......@@ -134,48 +134,6 @@ describe('wire service', () => {
return expect(WireService.send({amount: 1})).resolves.toBeUndefined();
});
it('it should send credit card wire', async(done) => {
const fakePayload = {
type: 'creditcard',
token: 10
};
api.post.mockResolvedValue({postresult: 1});
BlockchainWalletService.selectCurrent.mockResolvedValue(fakePayload);
try {
const result = await WireService.send({amount: 1, guid: 123123123});
// expect post to be called with the payload
expect(api.post).toBeCalledWith(`api/v1/wire/123123123`, {
amount: 1,
method: 'tokens',
payload: {
address: 'offchain',
method: 'creditcard',
token: 10
},
recurring: false
});
// expect to return the post result with the payload added
expect(result).toEqual({
postresult: 1,
payload: {
address: 'offchain',
method: 'creditcard',
token: 10
}
});
} catch(e) {
done.fail(e);
}
done();
});
it('it should send offchain wire', async(done) => {
const fakePayload = {
......@@ -187,12 +145,12 @@ describe('wire service', () => {
BlockchainWalletService.selectCurrent.mockResolvedValue(fakePayload);
try {
const result = await WireService.send({amount: 1, guid: 123123123});
const result = await WireService.send({amount: 1, guid: 123123123, currency: 'tokens'});
// expect post to be called with the payload
expect(api.post).toBeCalledWith(`api/v1/wire/123123123`, {
expect(api.post).toBeCalledWith(`api/v2/wire/123123123`, {
amount: 1,
method: 'tokens',
method: 'offchain',
payload: {
method: 'offchain',
address: 'offchain'
......@@ -230,12 +188,12 @@ describe('wire service', () => {
BlockchainWireService.create.mockResolvedValue('0xtxhash');
try {
const result = await WireService.send({amount: 1, guid: 123123123, owner: {eth_wallet: '0xaddress'}});
const result = await WireService.send({amount: 1, guid: 123123123, currency: 'tokens', owner: {eth_wallet: '0xaddress'}});
// expect post to be called with the payload
expect(api.post).toBeCalledWith(`api/v1/wire/123123123`, {
expect(api.post).toBeCalledWith(`api/v2/wire/123123123`, {
amount: 1,
method: 'tokens',
method: 'onchain',
payload: {
method: 'onchain',
address: fakePayload.wallet.address,
......@@ -279,12 +237,12 @@ describe('wire service', () => {
BlockchainTokenService.increaseApproval.mockResolvedValue(true);
try {
const result = await WireService.send({amount: 1,recurring: true, guid: 123123123, owner: {eth_wallet: '0xaddress'}});
const result = await WireService.send({amount: 1, recurring: true, guid: 123123123, currency: 'tokens', owner: {eth_wallet: '0xaddress'}});
// expect post to be called with the payload
expect(api.post).toBeCalledWith(`api/v1/wire/123123123`, {
expect(api.post).toBeCalledWith(`api/v2/wire/123123123`, {
amount: 1,
method: 'tokens',
method: 'onchain',
payload: {
method: 'onchain',
address: fakePayload.wallet.address,
......@@ -319,7 +277,7 @@ describe('wire service', () => {
});
it('should throw error when try to send without an address', () => {
return expect(WireService.send({amount: 1, guid: 123123123, owner: {eth_wallet: null}}))
return expect(WireService.send({amount: 1, guid: 123123123, currency: 'tokens', owner: {eth_wallet: null}}))
.rejects.toThrowError('User cannot receive OnChain tokens because they haven\'t setup an OnChain address. Please retry OffChain.');
});
......@@ -327,7 +285,7 @@ describe('wire service', () => {
// return an unexpected payload type
BlockchainWalletService.selectCurrent.mockResolvedValue({type: 'unexpected'});
return expect(WireService.send({amount: 1, guid: 123123123, owner: {eth_wallet: '0xsome'}}))
return expect(WireService.send({amount: 1, guid: 123123123, currency: 'tokens', owner: {eth_wallet: '0xsome'}}))
.rejects.toThrowError('Unknown type');
});
......
......@@ -15,26 +15,30 @@ describe('wire store', () => {
store = new WireStore();
});
it('should set the guid', () => {
it('should set the owner', () => {
const owner = {name: 'someone', guid: '123123'}
// should have a default null
expect(store.guid).toBe(null);
expect(store.owner).toBe(null);
store.setGuid('123123');
store.setOwner(owner);
// should change to the new value
expect(store.guid).toBe('123123');
expect(store.owner).toEqual(owner);
});
it('should set the owner', () => {
it('should set the guid of the owner', () => {
// should have a default null
expect(store.owner).toBe(null);
const owner = {guid: '123123'};
expect(store.guid).toBe(undefined);
store.setOwner({name: 'someone'});
store.setOwner(owner);
// should change to the new value
expect(store.owner).toEqual({name: 'someone'});
expect(store.guid).toBe('123123');
});
it('should set the amount', () => {
// should have a default 1
expect(store.amount).toBe(1);
......@@ -79,17 +83,18 @@ describe('wire store', () => {
})
it('should load the user rewards from the service', async (done) => {
const fakeOwner = {name: 'someone'};
const fakeGuid = '123123';
const fakeOwner = {name: 'someone', guid: '123123'};
const fakeOwnerResponse = {name: 'someone', guid: '123123'};
wireService.userRewards.mockResolvedValue(fakeOwner);
wireService.userRewards.mockResolvedValue(fakeOwnerResponse);
try {
const result = await store.loadUser(fakeGuid);
store.setOwner(fakeOwner);
const result = await store.loadUserRewards();
// should return the owner
expect(result).toEqual(fakeOwner);
expect(result).toEqual(fakeOwnerResponse);
// should set the owner
expect(store.owner).toEqual(fakeOwner);
expect(store.owner).toEqual(fakeOwnerResponse);
} catch (e) {
done.fail(e);
}
......@@ -97,12 +102,11 @@ describe('wire store', () => {
});
it('should not set the owner if load rewards fails', async (done) => {
const fakeGuid = '123123';
wireService.userRewards.mockRejectedValue(new Error('fakeError'));
try {
await store.loadUser(fakeGuid);
await store.loadUserRewards();
done.fail();
} catch (e) {
// should not set the owner
......@@ -128,19 +132,29 @@ describe('wire store', () => {
it('should reset the obvservable values', () => {
store.amount = 2;
store.currency = 'usd';
store.sending = true;
store.owner = {};
store.recurring = true;
store.guid = '123123';
store.showBtc = true;
store.showCardselector = true;
store.loaded = true;
store.errors = [{message:'error'}];
store.reset();
// should reset all
expect(store.amount).toEqual(1);
expect(store.sending).toEqual(false);
expect(store.showBtc).toEqual(false);
expect(store.showCardselector).toEqual(false);
expect(store.loaded).toEqual(false);
expect(store.owner).toEqual(null);
expect(store.currency).toEqual('tokens');
expect(store.errors).toEqual([]);
expect(store.recurring).toEqual(false);
expect(store.guid).toEqual(null);
expect(store.guid).toEqual(undefined);
});
it('should return if already sending', () => {
......@@ -161,6 +175,8 @@ describe('wire store', () => {
() => expect(store.sending).toEqual(true)
);
store.setOwner({guid: '123123', name: 'someone'});
const result = await store.send();
// should return the service call result
......@@ -171,10 +187,12 @@ describe('wire store', () => {
// should call the service
expect(wireService.send).toBeCalledWith({
currency: 'tokens',
amount: store.amount,
guid: store.guid,
owner: store.owner,
recurring: store.recurring
recurring: store.recurring,
paymentMethodId: null
});
done();
......@@ -196,18 +214,20 @@ describe('wire store', () => {
() => expect(store.sending).toEqual(true)
);
await store.send();
store.setOwner({guid: '123123', name: 'someone'});
await store.send();
done.fail('should fail');
} catch (e) {
// should call the service
expect(wireService.send).toBeCalledWith({
currency: 'tokens',
amount: store.amount,
guid: store.guid,
owner: store.owner,
recurring: store.recurring
recurring: store.recurring,
paymentMethodId: null
});
// should set sending in false on finish
......
......@@ -79,7 +79,7 @@ project.ext.react = [
]
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-sentry/sentry.gradle"
apply from: "../../node_modules/@sentry/react-native/sentry.gradle"
/**
* Set this to true to create two separate APKs instead of one:
......@@ -163,8 +163,10 @@ android {
}
dependencies {
implementation project(':@sentry_react-native')
implementation project(':tipsi-stripe')
implementation project(':react-native-svg')
implementation project(':react-native-device-info')
implementation project(':react-native-sentry')
implementation project(':react-native-notifications')
implementation project(':@react-native-community_netinfo')
implementation project(':react-native-screens')
......
No preview for this file type
File added
No preview for this file type
......@@ -3,8 +3,10 @@ package com.minds.mobile;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import io.sentry.RNSentryPackage;
import com.gettipsi.stripe.StripeReactPackage;
import com.horcrux.svg.SvgPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.reactnativejitsimeet.JitsiMeetPackage;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import com.reactnativecommunity.netinfo.NetInfoPackage;
......@@ -58,8 +60,10 @@ public class MainApplication extends Application implements ShareApplication, Re
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNDeviceInfo(),
new RNSentryPackage(),
new StripeReactPackage(),
new SvgPackage(),
new RNDeviceInfo(),
new JitsiMeetPackage(),
new NetInfoPackage(),
new RNScreensPackage(),
......
......@@ -35,7 +35,7 @@ allprojects {
url "$rootDir/../node_modules/react-native/android"
}
maven { url "https://jitpack.io" }
maven { url "https://www.jitpack.io" }
}
configurations.all {
resolutionStrategy.force "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
......
......@@ -22,10 +22,10 @@ org.gradle.jvmargs=-Xmx2048m
systemProp.org.gradle.internal.http.connectionTimeout=180000
systemProp.org.gradle.internal.http.socketTimeout=180000
versionName=3.9.1
versionName=3.10.0
# CUSTOM
versionCode=1050000013
versionCode=1050000014
# PLAY STORE
# versionCode=310031
# versionCode=310032
rootProject.name = 'Minds'
include ':@sentry_react-native'
project(':@sentry_react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@sentry/react-native/android')
include ':tipsi-stripe'
project(':tipsi-stripe').projectDir = new File(rootProject.projectDir, '../node_modules/tipsi-stripe/android')
include ':react-native-svg'
project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
include ':react-native-device-info'
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
include ':react-native-sentry'
project(':react-native-sentry').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sentry/android')
include ':react-native-jitsi-meet'
project(':react-native-jitsi-meet').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-jitsi-meet/android')
include ':react-native-notifications'
......
source "https://rubygems.org"
gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
......@@ -64,6 +64,7 @@ GEM
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-sentry (1.5.0)
gh_inspector (1.1.3)
google-api-client (0.23.9)
addressable (~> 2.5, >= 2.5.1)
......@@ -154,6 +155,7 @@ PLATFORMS
DEPENDENCIES
fastlane
fastlane-plugin-sentry
BUNDLED WITH
2.0.2
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.9.1</string>
<string>3.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.9.1</string>
<string>3.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
This diff is collapsed.
......@@ -10,11 +10,6 @@
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#if __has_include(<React/RNSentry.h>)
#import <React/RNSentry.h> // This is used for versions of react >= 0.40
#else
#import "RNSentry.h" // This is used for versions of react < 0.40
#endif
#import <React/RCTLinkingManager.h>
@implementation AppDelegate
......@@ -25,8 +20,6 @@
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"Minds"
initialProperties:nil];
[RNSentry installWithRootView:rootView];
rootView.backgroundColor = [UIColor whiteColor];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
......
......@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.9.1</string>
<string>3.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.9.1</string>
<string>3.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -37,4 +37,27 @@ platform :ios do
lane :testflight do
upload_to_testflight(skip_waiting_for_build_processing: true)
end
desc "Prepare sentry release"
lane :preparesentry do
version = get_version_number(
xcodeproj: "Minds.xcodeproj",
target: "Minds"
)
sh("echo " + version + " > ../version")
sentry_upload_dsym(
org_slug: 'minds-inc',
project_slug: 'mobile',
dsym_path: 'Minds.app.dSYM.zip'
)
sentry_create_release(
org_slug: 'minds-inc',
project_slug: 'mobile',
version: version ,
finalize: false
)
end
end
# Autogenerated by fastlane
#
# Ensure this file is checked in to source control!
gem 'fastlane-plugin-sentry'
......@@ -21,11 +21,21 @@ or alternatively using `brew cask install fastlane`
fastlane ios test
```
Run all the tests
### ios beta
### ios buildrelease
```
fastlane ios beta
fastlane ios buildrelease
```
Build release
### ios testflight
```
fastlane ios testflight
```
Push a new beta build to TestFlight
### ios preparesentry
```
fastlane ios preparesentry
```
Prepare sentry release
----
......
......@@ -66,7 +66,7 @@
"balance":"Balance",
"gasEth":"Gas (ETH)",
"walletNameExample":"eg. Mobile Spending",
"receiverAddress":"Recieve Address",
"receiverAddress":"Receive Address",
"shouldBeReceiver":"Should this address be your receiver address for Wire & Boost?",
"downloadPrivate":"Download the private key of this wallet",
"deleteWarning":"WARNING: this is irreversable and all tokens will be lost",
......@@ -189,7 +189,7 @@
"saveChanges":"Save your changes",
"editChannel":"Edit your channel settings",
"sendMessage":"Send a message to this channel",
"message":"MESSAGE",
"message":"Message",
"mature":"This channel contains mature content",
"confirmUnsubscribe":"Are you sure you want to unsubscribe from this channel?",
"subscribe":"Subscribe",
......@@ -206,7 +206,8 @@
"createFirstPost":"Create your first post",
"blocked":"You have blocked @{{username}}",
"tapUnblock":"Tap to unblock",
"notFound":"Channel not found"
"notFound":"Channel not found",
"viewScheduled":"Scheduled"
},
"discovery":{
"search":"Search...",
......@@ -323,6 +324,7 @@
"dontHave":"You don't have any notifications",
"rewardsStateIncrease": "Congratulations! You just became a {{state}} user.\nWe've increased your daily token reward multiplier to {{multiplier}}x",
"rewardsStateDecrease": "We miss you! You just dropped down to a {{state}} user.\nYour daily token reward multiplier has been reduced to {{multiplier}}x",
"rewardsStateDecreaseToday": "It's not too late! You will drop down to a {{state}} user by the end of today and your daily token reward multiplier will reduce to {{multiplier}}x. Get liking and commenting to bring your score back up!",
"rewardsSummary": "You earned {{amount}} tokens yesterday."
},
"notificationSettings":{
......@@ -349,6 +351,7 @@
"referral_complete": "Referral Complete"
},
"payments":{
"confirmDeleteCard":"Do you want to delete this card?",
"cardExpirationMonth":"Card Expiration Month",
"cardExpirationYear":"Card Expiration Year",
"yourSavedCard":"YOUR SAVED CARDS:",
......@@ -521,6 +524,7 @@
"yourShare":"Your Share",
"yourScore":"Your Score",
"yourRewardFactor": "Your Reward Multiplier",
"yourUserState": "Your User State",
"networkScore":"Network Score",
"transactionsTitle":"Transactions",
"transactionsDescription":"A list of transactions you have made with your addresses",
......@@ -556,13 +560,17 @@
}
},
"wire":{
"amountMonth":"{{amount}}/month",
"customDonation":"Custom Donation",
"selectCredit":"Select a credit card.",
"willNotRecur":"You can send {{currency}} to this user, however it will not recur.",
"amountMonth":"{{amount}} / month",
"noAddress":"This channel has not configured their {{type}} address yet",
"amountMonthDescription":"THIS POST CAN ONLY BE SEEN BY SUPPORTERS WHO WIRE {{amount}}/MONTH TO @{{name}}",
"weHaveReceivedYourTransaction":"We've received your transaction",
"pleaseTryUnlockingMessage":"Please try unlocking this post after it gets processed. We estimate it may take around 5 minutes.",
"onlySupportersWhoWire":"Only supporters who wire you over {{amount}}/month will see this post.",
"wireMeOver":"Wire me over {{amount}}/month to see this post.",
"supportMessage":"Support &{name}& by sending them tokens. Once you send them the amount listed in the tiers, you can receive rewards if they are offered. Otherwise, it's a donation.",
"supportMessage":"Support &{name}& by sending them {{payments}}. Once you send them the amount listed in the tiers, you can receive rewards if they are offered. Otherwise, it's a donation.",
"repeatMessage":"Repeat this transaction every month",
"youHaveSent":"You have sent &{amount}& in the last month.",
"confirmMessage":"You will send {{amount}} to @{{name}}",
......@@ -718,6 +726,7 @@
"hide":"Hide",
"ops":"Oops",
"pin":"Pin",
"more":"More",
"unpin":"Unpin",
"downloadGallery":"Download to gallery",
"wantToDownloadImage":"Do you want to download this image?",
......
......@@ -172,7 +172,7 @@
"saveChanges": "Guarda tus cambios",
"editChannel": "Cambia la configuración de tu canal",
"sendMessage": "Envia un mensaje a este canal",
"message": "MENSAJE",
"message": "Mensaje",
"subscribe": "Suscribir",
"subscribeMessage": "Suscribirse a este canal",
"unsubscribeMessage": "Cancelar suscripción de este canal",
......@@ -189,7 +189,8 @@
"subscribed": "Suscrito",
"blocked": "Has bloqueado a @{{username}}",
"tapUnblock": "Pulsa para desbloquear",
"notFound": "Canal no encontrado"
"notFound": "Canal no encontrado",
"viewScheduled": "Agendado"
},
"discovery": {
"search": "Buscar...",
......@@ -217,9 +218,9 @@
"banConfirm": "Estás seguro? Quieres suspender a este usuario?",
"errorLoading": "Error cargando grupos",
"confirmKick": "Estás seguro? Quieres expulsar a este usuario?",
"listMembersCount": "Miembros",
"disableConversations":"Inhabilitar conversaciones",
"enableConversations":"Habilitar conversaciones"
"listMembersCount": "Miembros {{count}}",
"disableConversations": "Desactivar conversaciones",
"enableConversations": "Activar conversaciones"
},
"keychain": {
"unlockMessage": "Desbloquear {{keychain}} keychain",
......@@ -297,7 +298,11 @@
"welcomeChat": "Chatea seguro con tus subscripciones mutuas",
"repliedCommentOn": "contesto tu comentario en",
"groupComment": "Grupo {{title}}",
"tagConversation": "te etiquetó en una conversación"
"tagConversation": "te etiquetó en una conversación",
"rewardsStateIncrease": "Felicitaciones! Ahora eres un usuario {{state}}.\nHemos incrementedo tu multiplicador de rewards diarios a {{multiplier}}x",
"rewardsStateDecrease": "Te extrañamos! Has bajado a un usuario {{state}}.\nHemos reducido tu multiplicador de rewards diarios a {{multiplier}}x",
"rewardsStateDecreaseToday": "No es tarde! Tu bajaras a un usuario {{state}} para el final del día y tu multiplicador diario de reward se reducira a {{multiplier}}x. Comienza a votar y comentar para recuperar tu puntaje.",
"rewardsSummary": "Ganaste {{amount}} tokens ayer."
},
"notificationSettings": {
"enableDisable": "Activa y desactiva las notificaciones",
......@@ -329,7 +334,8 @@
"cardConfirmMessage": "{{confirmMessage}} ¿Quieres usar esta tarjeta de crédito?",
"orUseAntoherCard": "O USAR OTRA TARJETA",
"enterCardDetails": "INGRESAR LA INFORMACIÓN DE TU TARJETA",
"yourSavedCard": "TUS TARJETAS GUARDADAS:"
"yourSavedCard": "TUS TARJETAS GUARDADAS:",
"confirmDeleteCard": "Quieres borrar esta tarjeta?"
},
"reports": {
"explain": "Por favor explica porque tu deseas reportar este contenido en pocas palabras",
......@@ -499,7 +505,9 @@
},
"inviteFriend": "Invitar un amigo",
"inviteFriendDescription": "Invitar un amigo y ganar recompensas",
"transactionsEmpty": "Tu lista de transacciones está vacía"
"transactionsEmpty": "Tu lista de transacciones está vacía",
"yourRewardFactor": "Tu Multiplicador de Reward",
"yourUserState": "Tu Estado de Usuario"
},
"wire": {
"amountMonth": "{{amount}}/month",
......@@ -516,7 +524,11 @@
"selectWalletMessage": "Selecciona la billetera que te gustaria usar para este Wire.",
"preApproveMessage": "Necesitamos que tu pre-aprueves una billetera Minds Wire para transacciones recurrentes en wire.",
"nameReward": "recompensas de {{name}}",
"addressLabel": "{{label}} Dirección"
"addressLabel": "{{label}} Dirección",
"customDonation": "Donación",
"selectCredit": "Selecciona una tarjeta",
"willNotRecur": "Tu puedes enviar {{currency}} a este usuario. pero no sera recurrente.",
"noAddress": "Este canal no ha configurado su cuenta {{type}} aún."
},
"validation": {
"email": "Email Inválido",
......@@ -748,10 +760,11 @@
"wantToUpdate": "Quieres actualizar la aplicación? ",
"updateAvailable": "Actualización disponible",
"rememberTomorrow": "Recuerdame más tarde",
"enableComments": "Habilitar comentarios",
"disableComments": "Inhabilitar comentarios",
"noInternet": "Sin conexión a internet",
"offline": "Fuera de línea",
"cantReachServer": "No puedo conectarme al servidor",
"showingStored": "Mostrando datos guardados"
"showingStored": "Mostrando datos guardados",
"enableComments": "Activar comentarios",
"disableComments": "Desactivar comentarios",
"more": "Más"
}
\ No newline at end of file
......@@ -10,11 +10,13 @@
"locale": "ts-node tasks/poeditor.js",
"e2e": "jest -c jest.e2e.config.js",
"e2e-local": "e2elocal=1 jest -c jest.e2e.config.js",
"preinstall": "git config core.hooksPath .githooks",
"postinstall": "echo \"Fixing web3 require() issue…\" && replace \"c. . .rypto\" \"crypto\" node_modules -r --include=\"bytes.js\" && rn-nodeify --install --hack"
},
"dependencies": {
"@react-native-community/async-storage": "^1.5.1",
"@react-native-community/netinfo": "^3.2.1",
"@sentry/react-native": "^1.0.6",
"@tradle/react-native-http": "^2.0.1",
"@types/react": "^16.8.17",
"@types/react-native": "^0.57.53",
......@@ -72,7 +74,7 @@
"react-native-keep-awake": "^2.0.6",
"react-native-level-fs": "^3.0.0",
"react-native-localize": "^1.1.2",
"react-native-material-menu": "^0.4.2",
"react-native-material-menu": "^1.0.0",
"react-native-media-meta": "^0.0.10",
"react-native-minds-encryption": "https://github.com/Minds/react-native-minds-encryption",
"react-native-modal": "^9.0.0",
......@@ -83,17 +85,18 @@
"react-native-photo-view": "alwx/react-native-photo-view#e28f5416574cbfa07b2d4fa862c0048df56f7b02",
"react-native-progress": "^3.4.0",
"react-native-qrcode": "^0.2.6",
"react-native-qrcode-svg": "^5.2.0",
"react-native-randombytes": "^3.4.0",
"react-native-sentry": "^0.43.2",
"react-native-share": "1.1.2",
"react-native-share-menu": "Minds/react-native-share-menu",
"react-native-simple-radio-button": "^2.7.1",
"react-native-snap-carousel": "^3.5.0",
"react-native-snap-carousel": "^3.8.0",
"react-native-sqlite-storage": "^3.3.10",
"react-native-svg": "^9.8.4",
"react-native-switch-pro": "^1.0.2-beta",
"react-native-tcp": "3.3.0",
"react-native-udp": "^2.3.1",
"react-native-vector-icons": "^6.4.2",
"react-native-vector-icons": "^6.6.0",
"react-native-video": "react-native-community/react-native-video#c36a9cf2efdb920b86ca453e1be89398df3dae16",
"react-native-webview": "^5.11.0",
"react-navigation": "^3.11.0",
......@@ -106,8 +109,8 @@
"socket.io-client": "^2.0.4",
"stream-browserify": "^1.0.0",
"string_decoder": "^0.10.31",
"stripe-client": "^1.1.3",
"timers-browserify": "^1.0.1",
"tipsi-stripe": "8.0.0-beta.6",
"tty-browserify": "0.0.0",
"url": "~0.10.1",
"util": "~0.10.3",
......@@ -126,6 +129,8 @@
"enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.3.5",
"eslint-plugin-flowtype": "^2.50.0",
"eslint-plugin-react": "^7.14.3",
"eslint-plugin-react-native": "^3.7.0",
"flow-bin": "^0.92.0",
"jest": "24.1.0",
"jest-serializer-enzyme": "^1.0.0",
......
......@@ -7,6 +7,12 @@ class BlockchainWireService {
return await Web3Service.getContract('wire');
}
/**
* Create an onchain tokens wireß
* @param {string} receiver
* @param {string} tokensAmount
* @param {string} message
*/
async create(receiver, tokensAmount, message = '') {
const token = await BlockchainTokenService.getContract(),
wireAddress = (await this.getContract()).options.address;
......@@ -24,6 +30,21 @@ class BlockchainWireService {
return result.transactionHash;
}
/**
* Create a eth wire
* @param {string} receiver
* @param {string} tokensAmount
*/
async createEth(receiver, tokensAmount) {
const result = await Web3Service.sendEth(
receiver,
tokensAmount
);
return result.transactionHash;
}
}
export default new BlockchainWireService();
......@@ -126,6 +126,36 @@ class Web3Service {
throw new Error('E_CANCELLED');
}
}
/**
* Send ETH from the selected wallet to the destination address
* @param {string} to destination ETH address
* @param {number} amount eth amount
*/
async sendEth(to, amount) {
const toHex = this.web3.utils.toHex;
const baseOptions = await this.getTransactionOptions();
const privateKey = await BlockchainWalletService.unlock(baseOptions.from);
const nonce = await this.web3.eth.getTransactionCount(baseOptions.from);
const tx = {
nonce,
to,
from: baseOptions.from,
value: toHex( this.web3.utils.toWei(amount, 'ether') ),
gas: toHex(21000),
gasPrice: toHex( this.web3.utils.toWei('2', 'Gwei') ), // converts the gwei price to wei
}
const signedTx = sign(tx, privateKey);
return await new Promise((resolve, reject) => {
this.web3.eth.sendSignedTransaction(signedTx)
.once('transactionHash', hash => resolve({ transactionHash: hash }))
.once('error', e => reject(e));
});
}
}
export default new Web3Service();
......@@ -11,7 +11,6 @@ let dispose;
const DEFAULT_OPTS = {
signable: false,
offchain: false,
buyable: false,
};
/**
......
......@@ -167,7 +167,7 @@ export class BlockchainWalletService {
async getCurrent(onlyWithPrivateKey = false) {
if (!this.current || (!this.current.privateKey && onlyWithPrivateKey)) {
const payload = await this.selectCurrent(i18n.t('blockchain.selectTheWallet'), { signable: onlyWithPrivateKey, offchain: false, buyable: false });
const payload = await this.selectCurrent(i18n.t('blockchain.selectTheWallet'), { signable: onlyWithPrivateKey, offchain: false});
if (payload && payload.type === 'onchain') {
this.current = payload.wallet;
......@@ -205,7 +205,7 @@ export class BlockchainWalletService {
};
}
if (payload.type === 'onchain') {
if (payload.type === 'onchain' || payload.type === 'eth') {
this.current = payload.wallet;
await saveCurrentWalletAddressToStorage(payload.wallet.address);
}
......
......@@ -22,7 +22,7 @@ class BlockchainWalletStore {
.filter(wallet => !!wallet.privateKey);
}
getList(signableOnly, allowOffchain, allowCreditCard) {
getList(signableOnly, allowOffchain) {
const wallets = (signableOnly ? this.signableWallets : this.wallets).slice();
if (allowOffchain) {
......@@ -33,14 +33,6 @@ class BlockchainWalletStore {
})
}
/*if (allowCreditCard) {
wallets.push({
address: 'creditcard',
alias: 'Credit Card',
creditcard: true
})
}*/
return wallets;
}
......
......@@ -15,7 +15,7 @@ import { btoa } from 'abab';
import Share from 'react-native-share';
import QRCode from 'react-native-qrcode';
import QRCode from 'react-native-qrcode-svg';
import TransparentButton from '../../../common/components/TransparentButton';
import Touchable from '../../../common/components/Touchable';
......
......@@ -2,7 +2,6 @@ import React, { Component } from 'react';
import {
View,
Text,
TextInput,
Alert,
StyleSheet,
} from 'react-native';
......@@ -16,6 +15,8 @@ import Web3Service from '../../services/Web3Service';
import { ComponentsStyle } from '../../../styles/Components';
import i18n from '../../../common/services/i18n.service';
import TextInput from '../../../common/components/TextInput';
function addressExcerpt(address) {
return `0×${address.substr(2, 5)}...${address.substr(-5)}`;
}
......
......@@ -61,7 +61,6 @@ export default class BlockchainWalletList extends Component {
const wallets = this.props.blockchainWallet.getList(
this.props.signableOnly,
this.props.allowOffchain,
this.props.allowCreditCard
);
return (
......
......@@ -22,7 +22,7 @@ import currency from '../../../common/helpers/currency';
import BlockchainApiService from '../../BlockchainApiService';
import i18n from '../../../common/services/i18n.service';
@inject('blockchainWallet', 'blockchainWalletSelector', 'checkoutModal')
@inject('blockchainWallet', 'blockchainWalletSelector')
@observer
export default class BlockchainWalletModalScreen extends Component {
async select(wallet) {
......@@ -39,35 +39,26 @@ export default class BlockchainWalletModalScreen extends Component {
payload = {
type: 'offchain'
};
} else if (opts.buyable && wallet.address == 'creditcard') {
const ccOpts = {};
if (opts.confirmTokenExchange) {
try {
const usd = await BlockchainApiService.getRate('tokens') * opts.confirmTokenExchange;
ccOpts.confirmMessage = i18n.t('blockchain.walletConfirmMessage', {currency: currency(usd, 'usd'), tokens: currency(opts.confirmTokenExchange, 'tokens')});
} catch (e) {
ccOpts.confirmMessage = i18n.t('blockchain.walletNotDeterminedMessage', {tokens: currency(opts.confirmTokenExchange, 'tokens')});
}
}
const ccPayload = await this.props.checkoutModal.show(ccOpts);
if (ccPayload === null) {
return;
}
payload = {
type: 'creditcard',
token: ccPayload
}
} else {
if (opts.signable && !wallet.privateKey) {
return;
}
let type;
switch(opts.currency) {
case 'tokens':
type = 'onchain';
break;
case 'eth':
type = 'eth';
break;
default:
throw new Error('BlockchainWalletModal: currency not supported '+ opts.currency);
}
payload = {
type: 'onchain',
type,
wallet: toJS(wallet)
}
}
......@@ -126,7 +117,6 @@ export default class BlockchainWalletModalScreen extends Component {
onSelect={this.selectAction}
signableOnly={opts.signable}
allowOffchain={opts.offchain}
allowCreditCard={opts.buyable}
/>
</View>
......
......@@ -88,8 +88,8 @@ export default class BlogsViewScreen extends Component {
const params = this.props.navigation.state.params;
try {
if (params.blog) {
if (params.blog._list && params.blog._list.metadataServie) {
params.blog._list.metadataServie.pushSource('single');
if (params.blog._list && params.blog._list.metadataService) {
params.blog._list.metadataService.pushSource('single');
}
this.props.blogsView.setBlog(params.blog);
......@@ -107,6 +107,14 @@ export default class BlogsViewScreen extends Component {
await this.props.blogsView.loadBlog(guid);
}
if (this.props.blogsView.blog && this.props.blogsView.blog._list) {
this.props.blogsView.blog._list.viewed.addViewed(
this.props.blogsView.blog,
this.props.blogsView.blog._list.metadataService
);
}
} catch (error) {
logService.exception(error);
Alert.alert(
......@@ -123,8 +131,8 @@ export default class BlogsViewScreen extends Component {
*/
componentWillUnmount() {
const blog = this.props.blogsView.blog;
if (blog._list && blog._list.metadataServie) {
blog._list.metadataServie.popSource();
if (blog._list && blog._list.metadataService) {
blog._list.metadataService.popSource();
}
this.props.blogsView.reset();
}
......
......@@ -47,7 +47,7 @@ class VisibleError extends Error {
/**
* Boost Screen
*/
@inject('user', 'checkoutModal')
@inject('user')
export default class BoostScreen extends Component {
textInput = void 0;
......@@ -462,16 +462,6 @@ export default class BoostScreen extends Component {
}
break;
case 'usd':
const token = await this.props.checkoutModal.show();
if (!token) {
return;
}
nonce = token;
break;
default:
throw new Error('Not supported');
}
......
......@@ -58,7 +58,8 @@ export default class CapturePoster extends Component {
selection: {
start:0,
end: 0
}
},
time_created: null,
};
/**
......@@ -278,10 +279,12 @@ export default class CapturePoster extends Component {
shareValue={this.state.share}
lockValue={this.state.lock}
nsfwValue={this.state.nsfw}
timeCreatedValue={this.state.time_created}
onMature={this.onMature}
onNsfw={this.onNsfw}
onShare={this.onShare}
onLocking={this.onLocking}
onScheduled={this.onScheduled}
/>
{attachment.hasAttachment && <View style={styles.preview}>
......@@ -385,7 +388,8 @@ export default class CapturePoster extends Component {
let newPost = {
message: text,
wire_threshold: this.state.lock
wire_threshold: this.state.lock,
time_created: this.formatTimeCreated()
};
......@@ -488,6 +492,20 @@ export default class CapturePoster extends Component {
onLocking = lock => {
this.setState({ lock });
}
onScheduled = timeCreated => {
this.setState({ time_created: timeCreated })
}
formatTimeCreated = () => {
let time_created;
if (this.state.time_created) {
time_created = new Date(this.state.time_created).getTime();
} else {
time_created = Date.now();
}
return Math.floor(time_created / 1000);
}
}
const styles = StyleSheet.create({
......
......@@ -18,6 +18,7 @@ import logService from '../common/services/log.service';
import { GOOGLE_PLAY_STORE } from '../config/Config';
import testID from '../common/helpers/testID';
import i18n from '../common/services/i18n.service';
import DateTimePicker from 'react-native-modal-datetime-picker';
@inject('capture')
......@@ -30,6 +31,7 @@ export default class CapturePosterFlags extends Component {
lockingModalVisible: false,
lock: false,
min: '0',
datePickerVisible: false,
};
// Lifecycle
......@@ -201,6 +203,32 @@ export default class CapturePosterFlags extends Component {
);
}
showDatePicker = () => {
this.setState({ datePickerVisible: true });
}
dismissDatePicker = () => {
this.setState({ datePickerVisible: false });
}
/**
* Is considered scheduled when time_created is setted and is gt current time
*/
isScheduled = () => {
let response = false;
if (this.props.timeCreatedValue) {
const timeCreatedValue = new Date(this.props.timeCreatedValue);
response = timeCreatedValue.getTime() > Date.now();
}
return response;
}
shouldRenderScheduler = () => {
const hasFeature = featuresService.has('post-scheduler');
return hasFeature && (this.isScheduled() || !this.timeCreatedValue);
}
// Locking (Wire Threshold)
showLockingModal = () => {
......@@ -272,6 +300,23 @@ export default class CapturePosterFlags extends Component {
}
}
schedulerDatePicker() {
return (
<DateTimePicker
isVisible={this.state.datePickerVisible}
onConfirm={this.onScheduled}
date={this.props.timeCreatedValue || new Date()}
onCancel={this.dismissDatePicker}
mode='datetime'
/>
)
}
onScheduled = time_created => {
this.props.onScheduled(time_created);
this.dismissDatePicker();
}
lockingModalPartial() {
if (this.props.hideLock) return null;
return (
......@@ -381,9 +426,19 @@ export default class CapturePosterFlags extends Component {
{...testID('Post lock button')}
/>
</Touchable>}
{this.shouldRenderScheduler() && <Touchable style={[styles.cell, styles.cell__last]} onPress={this.showDatePicker}>
<IonIcon
name="md-calendar"
color={this.isScheduled() ? Colors.primary : Colors.darkGreyed}
size={30}
{...testID('Post scheduler button')}
/>
</Touchable>}
{this.shareModalPartial()}
{this.lockingModalPartial()}
{this.hashsModal()}
{this.schedulerDatePicker()}
</View>
);
}
......
......@@ -7,7 +7,6 @@ import {
Image,
View,
ActivityIndicator,
Button,
StyleSheet
} from 'react-native';
......@@ -22,7 +21,12 @@ import i18n from '../common/services/i18n.service';
import ActionSheet from 'react-native-actionsheet';
import WireAction from '../newsfeed/activity/actions/WireAction';
import featuresService from '../common/services/features.service';
import sessionService from '../common/services/session.service';
import Button from '../common/components/Button';
import withPreventDoubleTap from '../common/components/PreventDoubleTap';
import { CommonStyle as CS } from '../styles/Common';
const ButtonCustom = withPreventDoubleTap(Button);
/**
* Channel Actions
*/
......@@ -34,12 +38,17 @@ export default class ChannelActions extends Component {
super(props)
this.state = {
selected: '',
scheduledCount: '',
}
this.handleSelection = this.handleSelection.bind(this);
}
showActionSheet() {
componentWillMount() {
this.getScheduledCount();
}
showActionSheet = () => {
this.ActionSheet.show();
}
......@@ -85,6 +94,94 @@ export default class ChannelActions extends Component {
}
}
toggleSubscription = () => {
this.props.store.channel.toggleSubscription();
}
/**
* Navigate To conversation
*/
navToConversation = () => {
if (this.props.navigation) {
this.props.navigation.push('Conversation', { conversation: { guid : this.props.store.channel.guid + ':' + sessionService.guid } });
}
}
openWire = () => {
this.props.navigation.navigate('WireFab', { owner: this.props.store.channel});
}
onEditAction = () => {
this.props.onEditAction();
}
onViewScheduledAction = async () => {
this.props.onViewScheduledAction();
}
getScheduledCount = async () => {
if (featuresService.has('post-scheduler')) {
const count = await this.props.store.feedStore.getScheduledCount();
this.setState({ scheduledCount: count });
}
}
shouldRenderScheduledButton = () => {
return featuresService.has('post-scheduler') && !this.state.edit;
}
/**
* Get Action Button, Message or Subscribe
*/
getActionButton() {
if (!this.props.store.loaded && sessionService.guid !== this.props.store.channel.guid )
return null;
if (sessionService.guid === this.props.store.channel.guid) {
const viewScheduledButton = this.shouldRenderScheduledButton() ? (
<ButtonCustom
onPress={this.onViewScheduledAction}
accessibilityLabel={i18n.t('channel.viewScheduled')}
text={`${i18n.t('channel.viewScheduled').toUpperCase()}: ${this.state.scheduledCount}`}
loading={this.state.saving}
inverted={this.props.store.feedStore.endpoint == this.props.store.feedStore.scheduledEndpoint ? true : undefined}
/> ) : null ;
return (
<View style={{ flexDirection: 'row', justifyContent: 'flex-end' }}>
{ viewScheduledButton }
<ButtonCustom
onPress={this.onEditAction}
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={this.props.editing ? i18n.t('channel.saveChanges') : i18n.t('channel.editChannel')}
text={this.props.editing ? i18n.t('save').toUpperCase() : i18n.t('edit').toUpperCase()}
loading={this.props.saving}
/>
</View>
);
} else if (!!this.props.store.channel.subscribed) {
return (
<ButtonCustom
onPress={ this.navToConversation }
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={i18n.t('channel.sendMessage')}
text={i18n.t('channel.message')}
/>
);
} else if (sessionService.guid !== this.props.store.channel.guid) {
return (
<ButtonCustom
onPress={this.toggleSubscription}
containerStyle={[CS.rowJustifyCenter, CS.marginLeft0x]}
accessibilityLabel={i18n.t('channel.subscribeMessage')}
text={i18n.t('channel.subscribe').toUpperCase()}
/>
);
} else if (this.props.store.isUploading) {
return (
<ActivityIndicator size="small" />
)
}
}
/**
* Render Header
*/
......@@ -94,9 +191,30 @@ export default class ChannelActions extends Component {
const showWire = !channel.blocked && !channel.isOwner() && featuresService.has('crypto');
return (
<View style={styles.wrapper}>
{!!showWire && <WireAction owner={this.props.store.channel} navigation={this.props.navigation}/>}
<Icon name="md-settings" style={ styles.icon } onPress={() => this.showActionSheet()} size={24} />
<View style={[CS.rowJustifyEnd, CS.marginTop2x]}>
{this.getActionButton()}
{!!showWire &&
<ButtonCustom
onPress={ this.openWire }
accessibilityLabel="Wire Button"
containerStyle={[CS.rowJustifyCenter]}
textStyle={[CS.marginLeft, CS.marginRight]}
icon="ios-flash"
text="Wire"
>
<Icon name='ios-flash' size={18} style={[CS.marginLeft, CS.colorPrimary]} />
</ButtonCustom>
}
{!channel.isOwner() &&
<ButtonCustom
onPress={ this.showActionSheet }
accessibilityLabel={i18n.t('more')}
containerStyle={[CS.rowJustifyCenter]}
textStyle={[CS.marginLeft, CS.marginRight]}
icon="ios-flash"
text={i18n.t('more')}
/>
}
<ActionSheet
ref={o => this.ActionSheet = o}
title={title}
......@@ -116,7 +234,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
width: featuresService.has('crypto') ? 60 : 40,
height: 40,
},
icon: {
......
......@@ -27,8 +27,12 @@ import { isNetworkFail } from '../common/helpers/abortableFetch';
*/
export default class ChannelFeedStore {
feedsEndpoint = 'feeds/container';
scheduledEndpoint = 'feeds/scheduled';
@observable filter = 'feed';
@observable showrewards = false;
@observable endpoint = 'feeds/container';
channel;
......@@ -88,13 +92,29 @@ export default class ChannelFeedStore {
if (refresh) this.feedStore.clear();
this.feedStore.setEndpoint(`api/v2/feeds/container/${this.guid}/${this.esFeedfilter}`)
this.feedStore.setEndpoint(`api/v2/${this.endpoint}/${this.guid}/${this.esFeedfilter}`)
.setLimit(12)
.fetchRemoteOrLocal();
return;
}
/**
* Get channel scheduled activities count
*/
async getScheduledCount() {
const count = await channelService.getScheduledCount(this.guid);
return count;
}
/**
* Get channel scheduled activities count
*/
async getScheduledCount() {
const count = await channelService.getScheduledCount(this.guid);
return count;
}
@action
clearFeed() {
this.filter = 'feed';
......@@ -110,7 +130,7 @@ export default class ChannelFeedStore {
}
this.feedStore.clear();
this.feedStore.setEndpoint(`api/v2/feeds/container/${this.guid}/${this.esFeedfilter}`)
this.feedStore.setEndpoint(`api/v2/${this.endpoint}/${this.guid}/${this.esFeedfilter}`)
.setLimit(12)
.fetchRemoteOrLocal();
......@@ -122,9 +142,15 @@ export default class ChannelFeedStore {
setFilter(filter) {
this.filter = filter;
this.feedStore.setEndpoint(`api/v2/feeds/container/${this.guid}/${this.esFeedfilter}`)
this.feedStore.setEndpoint(`api/v2/${this.endpoint}/${this.guid}/${this.esFeedfilter}`)
.setIsTiled(filter === 'images' || filter === 'videos')
.clear()
.fetchRemoteOrLocal();
}
@action
toggleScheduled() {
this.endpoint = this.endpoint == this.feedsEndpoint ? this.scheduledEndpoint : this.feedsEndpoint;
this.setFilter(this.filter);
}
}
......@@ -94,7 +94,9 @@ export default class ChannelScreen extends Component {
}
componentWillUnmount() {
this.disposeEnter.remove();
if (this.disposeEnter) {
this.disposeEnter.remove();
}
this.props.channel.garbageCollect();
this.props.channel.store(this.guid).markInactive();
}
......@@ -310,9 +312,9 @@ const styles = StyleSheet.create({
buttonscol: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
justifyContent: 'flex-start',
//width: 150,
alignSelf:'flex-end'
alignSelf:'flex-start'
},
carouselcontainer: {
flex: 1,
......@@ -331,13 +333,13 @@ const styles = StyleSheet.create({
flexDirection: 'row',
},
username: {
fontSize: 10,
fontSize: 14,
color: '#999'
},
name: {
fontWeight: '700',
fontFamily: 'Roboto',
fontSize: 20,
fontSize: 22,
letterSpacing: 0.5,
marginRight: 8,
color: '#444',
......
......@@ -168,6 +168,11 @@ class ChannelService {
throw new Error (i18n.t('errorMessage'));
})
}
async getScheduledCount(guid) {
const response = await api.get(`api/v2/feeds/scheduled/${guid}/count`);
return response.count;
}
}
export default new ChannelService();
......@@ -36,11 +36,11 @@ import imagePicker from '../../common/services/image-picker.service';
import Button from '../../common/components/Button';
import withPreventDoubleTap from '../../common/components/PreventDoubleTap';
import CompleteProfile from './CompleteProfile';
import featuresService from '../../common/services/features.service';
// prevent accidental double tap in touchables
const TouchableHighlightCustom = withPreventDoubleTap(TouchableHighlight);
const TouchableCustom = withPreventDoubleTap(Touchable);
const ButtonCustom = withPreventDoubleTap(Button);
/**
* Channel Header
......@@ -58,7 +58,7 @@ export default class ChannelHeader extends Component {
briefdescription: '',
name: '',
saving: false,
edit: false
edit: false,
};
uploads = {
......@@ -88,15 +88,6 @@ export default class ChannelHeader extends Component {
return this.props.store.channel.getAvatarSource('large');
}
/**
* Navigate To conversation
*/
_navToConversation() {
if (this.props.navigation) {
this.props.navigation.push('Conversation', { conversation: { guid : this.props.store.channel.guid + ':' + session.guid } });
}
}
_navToSubscribers() {
if (this.props.navigation) {
this.props.navigation.push('Subscribers', { guid : this.props.store.channel.guid });
......@@ -149,49 +140,8 @@ export default class ChannelHeader extends Component {
}
}
/**
* Get Action Button, Message or Subscribe
*/
getActionButton() {
const styles = this.props.styles;
if (!this.props.store.loaded && session.guid !== this.props.store.channel.guid )
return null;
if (session.guid === this.props.store.channel.guid) {
return (
<ButtonCustom
onPress={this.onEditAction}
accessibilityLabel={this.state.edit ? i18n.t('channel.saveChanges') : i18n.t('channel.editChannel')}
text={this.state.edit ? i18n.t('save').toUpperCase() : i18n.t('edit').toUpperCase()}
loading={this.state.saving}
/>
);
} else if (!!this.props.store.channel.subscribed) {
return (
<TouchableHighlightCustom
onPress={() => { this._navToConversation() }}
underlayColor='transparent'
style={[ComponentsStyle.button, ComponentsStyle.buttonAction, styles.bluebutton]}
accessibilityLabel={i18n.t('channel.sendMessage')}
>
<Text style={{ color: colors.primary }} > {i18n.t('channel.message')} </Text>
</TouchableHighlightCustom>
);
} else if (session.guid !== this.props.store.channel.guid) {
return (
<TouchableHighlightCustom
onPress={() => { this.subscribe() }}
underlayColor='transparent'
style={[ComponentsStyle.button, ComponentsStyle.buttonAction, styles.bluebutton]}
accessibilityLabel={i18n.t('channel.subscribeMessage')}
>
<Text style={{ color: colors.primary }} > {i18n.t('channel.subscribe').toUpperCase()} </Text>
</TouchableHighlightCustom>
);
} else if (this.props.store.isUploading) {
return (
<ActivityIndicator size="small" />
)
}
onViewScheduledAction = async () => {
this.props.store.feedStore.toggleScheduled();
}
subscribe() {
......@@ -297,12 +247,16 @@ export default class ChannelHeader extends Component {
</View>}
<Text style={styles.username}>@{channel.username}</Text>
</View>
<View style={styles.buttonscol}>
{ !channel.blocked && this.getActionButton() }
{ session.guid !== channel.guid?
<ChannelActions navigation={this.props.navigation} store={this.props.store} me={session}></ChannelActions> : <View></View>
}
</View>
</View>
<View style={styles.buttonscol}>
<ChannelActions
navigation={this.props.navigation}
store={this.props.store}
onEditAction={this.onEditAction}
onViewScheduledAction={this.onViewScheduledAction}
editing={this.state.edit}
saving={this.state.saving}
/>
</View>
{isEditable && <View style={styles.briefdescriptionTextInputView}>
<TextInput
......
......@@ -6,6 +6,8 @@ import {
MINDS_CDN_URI
} from '../config/Config';
import api from '../common/services/api.service';
/**
* Comment model
*/
......@@ -43,6 +45,9 @@ export default class CommentModel extends ActivityModel {
* @param {string} size
*/
getThumbSource(size = 'medium') {
if (this.thumbnails && this.thumbnails[size]) {
return {uri: this.thumbnails[size], headers: api.buildHeaders() };
}
// for gif use always the same size to take adventage of the cache (they are not resized)
if (this.isGif()) size = 'medium';
if (this.custom_type == 'batch') {
......
......@@ -16,6 +16,34 @@ const decodeUrn = (urn) => {
return obj;
}
/**
* Get a single comment
* @param {string} entity_guid
* @param {string} guid
* @param {string} parent_path
*/
export async function getComment(entity_guid, guid, parent_path) {
let response: any = await api.get(
`api/v2/comments/${entity_guid}/${guid}/${parent_path}`,
{
limit: 1,
reversed: false,
descending: true,
}
);
if (!response.comments || response.comments.length === 0) {
return null;
}
if (response.comments[0]._guid != guid) {
return null;
}
return response.comments[0];
}
/**
* Get comments
* @param {string} guid
......@@ -116,8 +144,8 @@ export function updateComment(guid, description) {
/**
* Enable/Disable comments
* @param {string} guid
* @param {boolean} state
* @param {string} guid
* @param {boolean} state
*/
export function toggleAllowComments(guid, state) {
return api.post(`api/v2/permissions/comments/${guid}`,{
......
......@@ -10,7 +10,8 @@ import {
getComments,
postComment,
updateComment,
deleteComment
deleteComment,
getComment
} from './CommentsService';
import Comment from './Comment';
......@@ -262,9 +263,9 @@ export default class CommentsStore {
}
try {
const response = await getComments(this.guid, this.getParentPath(), true, null, false, 1, guid);
if (response.comments && response.comments[0]) {
this.comments.push(CommentModel.create(response.comments[0]));
const comment = await getComment(this.guid, guid, this.getParentPath());
if (comment) {
this.comments.push(CommentModel.create(comment));
}
} catch(err) {
logService.exception('[CommentsStore] commentSocket', err);
......
......@@ -5,6 +5,7 @@ import {
action,
computed,
runInAction,
toJS,
} from 'mobx';
import _ from 'lodash';
import sessionService from './services/session.service';
......@@ -46,6 +47,15 @@ export default class BaseModel {
return this.__list;
}
toPlainObject() {
const plainEntity = toJS(this);
// remove references to the list
delete(plainEntity.__list);
return plainEntity;
}
/**
* Child models classes
*/
......@@ -204,7 +214,7 @@ export default class BaseModel {
}
getClientMetadata() {
return (this._list && this._list.metadataServie) ? this._list.metadataServie.getEntityMeta(this) : {};
return (this._list && this._list.metadataService) ? this._list.metadataService.getEntityMeta(this) : {};
}
/**
......
......@@ -35,6 +35,7 @@ import openUrlService from '../services/open-url.service';
import logService from '../services/log.service';
import testID from '../helpers/testID';
import i18n from '../services/i18n.service';
import api from '../services/api.service';
/**
* Activity
......@@ -96,7 +97,7 @@ export default class MediaView extends Component {
guid = this.props.entity.guid;
}
const source = {uri: `${mindsService.settings.cinemr_url}${guid}/360.mp4`};
const source = {uri: MINDS_API_URI + `api/v1/media/${guid}/play`, headers: api.buildHeaders() };
return (
<View style={styles.videoContainer}>
......
......@@ -90,7 +90,7 @@ export default class ModalPicker extends PureComponent {
<View style={[CommonStyle.backgroundWhite, { height, paddingBottom: 8 }]}>
<Text style={[CommonStyle.fontL, CommonStyle.textCenter, CommonStyle.backgroundPrimary, CommonStyle.padding2x, CommonStyle.colorWhite]}>{title}</Text>
<View style={[CommonStyle.flexContainer]}>
<Picker {...props} onValueChange={this.select} selectedValue={this.state.value} style={{flex:1}} itemStyle={CommonStyle.fontM}>
<Picker {...props} onValueChange={this.select} selectedValue={this.state.value} style={CommonStyle.flexContainer} itemStyle={CommonStyle.fontM}>
{items.map((item, i) => <Picker.Item key={i} label={item[labelField]} value={item[valueField]} /> )}
</Picker>
<View style={[CommonStyle.rowJustifyCenter]}>
......
......@@ -3,7 +3,7 @@
* based on https://github.com/apentle/react-native-cancelable-fetch/blob/master/index.js
*/
class Abort extends Error {
export class Abort extends Error {
constructor(...args) {
super(...args)
this.code = 'Abort'
......@@ -132,4 +132,8 @@ export const abort = function(tag) {
export const isNetworkFail = function (err) {
return (err instanceof TypeError && err.message === 'Network request failed')
}
export const isAbort = function(err) {
return err instanceof Abort;
}
\ No newline at end of file
import { Dimensions } from "react-native";
export default function (percentage) {
const { width: viewportWidth, height: viewportHeight } = Dimensions.get('window');
const value = (percentage * viewportWidth) / 100;
return {value: Math.round(value), viewportWidth, viewportHeight};
}
\ No newline at end of file
......@@ -3,5 +3,6 @@ export default {
logout: jest.fn(),
isLoggedIn: jest.fn(),
setInitialScreen: jest.fn(),
onLogin: jest.fn()
onLogin: jest.fn(),
onSession: jest.fn()
}
\ No newline at end of file
......@@ -17,6 +17,10 @@ export class ApiError extends Error {
}
}
export const isApiError = function(err) {
return err instanceof ApiError;
}
/**
* Api service
*/
......
import apiService from "./api.service";
// @flow
import FeedsService from "./feeds.service";
import sessionService from "./session.service";
// types
import type ActivityModel from "../../newsfeed/ActivityModel";
/**
* Boosted content service
*/
......@@ -24,7 +27,7 @@ class BoostedContentService {
/**
* Reload boosts list
*/
load = async() => {
load = async(): Promise<any> => {
await this.feedsService
.setLimit(12)
.setOffset(0)
......@@ -37,7 +40,7 @@ class BoostedContentService {
/**
* Fetch one boost
*/
fetch() {
fetch(): ?ActivityModel {
this.offset++;
if (this.offset >= this.boosts.length) {
......
import apiService from "./api.service";
import blockListService from "./block-list.service";
import { first } from "rxjs/operators";
import { BehaviorSubject } from "rxjs";
// @flow
import _ from 'lodash';
import apiService from "./api.service";
import sessionService from "./session.service";
import blockListService from "./block-list.service";
import GroupModel from "../../groups/GroupModel";
import UserModel from "../../channel/UserModel";
import BlogModel from "../../blogs/BlogModel";
import ActivityModel from "../../newsfeed/ActivityModel";
import stores from "../../../AppStores";
import { abort } from "../helpers/abortableFetch";
import entitiesStorage from "./sql/entities.storage";
import sessionService from "./session.service";
// types
import type { FeedRecordType } from "./feeds.service";
import type BaseModel from "../BaseModel";
const CACHE_TTL_MINUTES = 15;
......@@ -24,7 +24,7 @@ class EntitiesService {
/**
* @var {Map} entities
*/
entities: Map = new Map();
entities: Map<string, BaseModel> = new Map();
/**
* Contructor
......@@ -38,7 +38,7 @@ class EntitiesService {
* @param {string} urn
* @param {boolean} updateLast
*/
getFromCache(urn, updateLast = true) {
getFromCache(urn: string, updateLast: boolean = true): ?BaseModel {
const record = this.entities.get(urn)
if (record && updateLast) record.last = Date.now() / 1000;
return record ? record.entity : null;
......@@ -62,7 +62,7 @@ class EntitiesService {
* Delete an entity from the cache
* @param {string} urn
*/
deleteFromCache(urn) {
deleteFromCache(urn: string) {
this.entities.delete(urn);
entitiesStorage.remove(urn);
}
......@@ -73,13 +73,12 @@ class EntitiesService {
* @param {Mixed} abortTag
* @param {boolean} asActivities
*/
async getFromFeed(feed, abortTag, asActivities = false): Promise<EntityObservable[]> {
async getFromFeed(feed: Array<FeedRecordType>, abortTag: any, asActivities: boolean = false): Promise<Array<BaseModel>> {
if (!feed || !feed.length) {
return [];
}
const blockedGuids = blockListService.blocked;
let urnsToFetch = [];
const urnsToResync = [];
const entities = [];
......@@ -98,14 +97,14 @@ class EntitiesService {
}
}
// if we have urnstoFetch we try to load from the sql storage first
// if we have urnsToFetch we try to load from the sql storage first
if (urnsToFetch.length > 0) {
const localEntities = await entitiesStorage.readMany(urnsToFetch);
urnsToFetch = _.difference(urnsToFetch, localEntities.map(m => m.urn));
urnsToFetch = _.difference(urnsToFetch, localEntities.map((m: any): string => m.urn));
// we add to resync list
localEntities.forEach(e => {
urnsToResync.push(e.urn);
this.addEntity(e, false)
localEntities.forEach((entity: any) => {
urnsToResync.push(entity.urn);
this.addEntity(entity, false)
});
}
......@@ -146,7 +145,7 @@ class EntitiesService {
* @param {boolean} asActivities
* @return Object
*/
async single(urn: string, defaultEntity, asActivities = false): EntityObservable {
async single(urn: string, defaultEntity: BaseModel, asActivities: boolean = false): BaseModel {
if (!urn.startsWith('urn:')) { // not a urn, so treat as a guid
urn = `urn:activity:${urn}`; // and assume activity
}
......@@ -186,7 +185,7 @@ class EntitiesService {
* @param {boolean} asActivities
* @return []
*/
async fetch(urns: Array<string>, abortTag: any, asActivities = false): Promise<Array<Object>> {
async fetch(urns: Array<string>, abortTag: any, asActivities: boolean = false): Promise<Array<Object>> {
try {
const response: any = await apiService.get('api/v2/entities/', { urns, as_activities: asActivities ? 1 : 0}, abortTag);
......@@ -206,9 +205,8 @@ class EntitiesService {
* Add or resync an entity
* @param {Object} entity
* @param {boolean} store
* @return void
*/
addEntity(entity, store = true): void {
addEntity(entity: Object, store: boolean = true) {
this.cleanEntity(entity);
......@@ -225,13 +223,13 @@ class EntitiesService {
* Clean properties to save memory and storage space
* @param {Object} entity
*/
cleanEntity(entity) {
cleanEntity(entity: Object) {
if (entity['thumbs:up:user_guids']) {
entity['thumbs:up:user_guids'] = entity['thumbs:up:user_guids'].filter(guid => guid == sessionService.guid);
entity['thumbs:up:user_guids'] = entity['thumbs:up:user_guids'].filter((guid: string): boolean => guid == sessionService.guid);
}
if (entity['thumbs:down:user_guids']) {
entity['thumbs:down:user_guids'] = entity['thumbs:down:user_guids'].filter(guid => guid == sessionService.guid);
entity['thumbs:down:user_guids'] = entity['thumbs:down:user_guids'].filter((guid: string): boolean => guid == sessionService.guid);
}
}
......@@ -239,7 +237,7 @@ class EntitiesService {
* Map object to model
* @param {Object} entity
*/
mapToModel(entity) {
mapToModel(entity: Object): BaseModel {
switch (entity.type) {
case 'activity':
return ActivityModel.create(entity)
......
This diff is collapsed.
......@@ -5,7 +5,7 @@ import AsyncStorage from '@react-native-community/async-storage';
import storageService from './storage.service';
import settingsService from '../../settings/SettingsService'
import settingsStore from '../../settings/SettingsStore';
import { Sentry } from 'react-native-sentry';
import * as Sentry from '@sentry/react-native';
import { isNetworkFail } from '../helpers/abortableFetch';
import { ApiError } from './api.service';
......@@ -61,6 +61,14 @@ class LogService {
deviceLog.error(...args);
}
isApiError(error) {
return error instanceof ApiError;
}
isUnexpectedError(error) {
return !isNaN(error.status) && error.status >= 500;
}
exception(prepend, error) {
if (!error) {
......@@ -68,7 +76,7 @@ class LogService {
prepend = null;
}
if (!isNetworkFail(error) && !(error instanceof ApiError && error.status === 401)) {
if (!isNetworkFail(error) && (!this.isApiError(error) || this.isUnexpectedError(error))) {
// report the issue to sentry
Sentry.captureException(error);
}
......
......@@ -26,7 +26,7 @@ class StorageService {
return null;
}
value = await this._decryptIfNeeded(value);
value = await this._decryptIfNeeded(value, key);
return JSON.parse(value);
}
......@@ -36,7 +36,7 @@ class StorageService {
*
* @param {any} value
*/
async _decryptIfNeeded(value) {
async _decryptIfNeeded(value, key) {
if (value.startsWith(CRYPTO_AES_PREFIX)) {
const keychain = await AsyncStorage.getItem(`${STORAGE_KEY_KEYCHAIN_PREFIX}${key}`);
......
import stripe from 'tipsi-stripe';
import mindsService from './minds.service';
let intialized = false;
export const initStripe = async() => {
if (intialized) return;
intialized = true;
const settings = await mindsService.getSettings();
await stripe.setOptions({
publishableKey: settings.stripe_key,
//androidPayMode: 'test', // Android only
});
}
export default stripe;
import { observable, action } from 'mobx';
import { action } from 'mobx';
import {toggleComments, follow, unfollow, toggleFeatured, monetize, update, setViewed} from '../../newsfeed/NewsfeedService';
import { setViewed } from '../../newsfeed/NewsfeedService';
import channelService from '../../channel/ChannelService';
import OffsetListStore from './OffsetListStore';
import logService from '../services/log.service';
import { isNetworkFail } from '../helpers/abortableFetch';
/**
* Infinite scroll list that inform viewed
......@@ -41,13 +40,14 @@ export default class OffsetFeedListStore extends OffsetListStore {
async addViewed(entity) {
if (!this.viewed.get(entity.guid)) {
this.viewed.set(entity.guid, true);
let response;
try {
const meta = this.metadataServie ? this.metadataServie.getEntityMeta(entity) : {};
response = await setViewed(entity, meta);
const meta = this.metadataService ? this.metadataService.getEntityMeta(entity) : {};
await setViewed(entity, meta);
} catch (e) {
this.viewed.delete(entity.guid);
throw new Error('There was an issue storing the view');
if (!isNetworkFail(e)) {
throw e;
}
}
}
}
......
......@@ -23,10 +23,10 @@ class SingleEntityStore {
this.errorLoading = value;
}
async loadEntity(urn, defaultEntity) {
async loadEntity(urn, defaultEntity, asActivity = false) {
this.setErrorLoading(false);
try {
const entity = await entitiesService.single(urn, defaultEntity);
const entity = await entitiesService.single(urn, defaultEntity, asActivity);
this.setEntity(entity);
} catch (err) {
this.setErrorLoading(true);
......
......@@ -18,19 +18,18 @@ export default class Viewed {
/**
* Add an entity to the viewed list and inform to the backend
* @param {BaseModel} entity
* @param {MetadataService|undefined} metadataServie
* @param {MetadataService|undefined} metadataService
*/
async addViewed(entity, metadataServie) {
async addViewed(entity, metadataService) {
if (!this.viewed.get(entity.guid)) {
this.viewed.set(entity.guid, true);
let response;
try {
const meta = metadataServie ? metadataServie.getEntityMeta(entity) : {};
response = await setViewed(entity, meta);
const meta = metadataService ? metadataService.getEntityMeta(entity) : {};
await setViewed(entity, meta);
} catch (e) {
this.viewed.delete(entity.guid);
if (!isNetworkFail(e)) {
throw new Error('There was an issue storing the view');
throw e;
}
}
}
......
......@@ -118,8 +118,13 @@ export default class DiscoveryScreen extends Component {
* Dispose reactions of navigation store on unmount
*/
componentWillUnmount() {
this.disposeEnter.remove();
this.disposeLeave.remove();
if (this.disposeEnter) {
this.disposeEnter.remove();
}
if (this.disposeLeave) {
this.disposeLeave.remove();
}
}
/**
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.