...
 
Commits (36)
......@@ -202,8 +202,8 @@ workflows:
filters:
branches:
only:
- /^stable-.*/
- /^test-.*/
- /^stable-*/
- /^test-*/
- test/circle-ci
- sentry:
requires:
......
......@@ -6,17 +6,7 @@
* @flow
*/
import React, {
Component,
} from 'react';
import {
Provider,
} from 'mobx-react/native'; // import from mobx-react/native instead of mobx-react fix test
import NavigationService from './src/navigation/NavigationService';
import RNBootSplash from "react-native-bootsplash";
import React, { Component } from 'react';
import {
BackHandler,
Platform,
......@@ -26,10 +16,13 @@ import {
Alert,
Clipboard,
StatusBar,
UIManager,
} from 'react-native';
import { Provider, observer } from 'mobx-react/native';
import RNBootSplash from 'react-native-bootsplash';
import FlashMessage from 'react-native-flash-message';
import NavigationService from './src/navigation/NavigationService';
import KeychainModalScreen from './src/keychain/KeychainModalScreen';
import BlockchainTransactionModalScreen from './src/blockchain/transaction-modal/BlockchainTransactionModalScreen';
import NavigationStack from './src/navigation/NavigationStack';
......@@ -38,13 +31,10 @@ import './AppErrors';
import './src/common/services/socket.service';
import pushService from './src/common/services/push.service';
import mindsService from './src/common/services/minds.service';
import featureService from './src/common/services/features.service';
import receiveShare from './src/common/services/receive-share.service';
import sessionService from './src/common/services/session.service';
import deeplinkService from './src/common/services/deeplinks-router.service';
import badgeService from './src/common/services/badge.service';
import authService from './src/auth/AuthService';
import NotificationsService from './src/notifications/NotificationsService';
import getMaches from './src/common/helpers/getMatches';
import { GOOGLE_PLAY_STORE } from './src/config/Config';
import updateService from './src/common/services/update.service';
......@@ -63,6 +53,7 @@ import * as Sentry from '@sentry/react-native';
import apiService from './src/common/services/api.service';
import boostedContentService from './src/common/services/boosted-content.service';
import translationService from './src/common/services/translation.service';
import ThemedStyles from './src/styles/ThemedStyles';
let deepLinkUrl = '';
......@@ -74,8 +65,10 @@ pushService.init();
// fire sqlite init
sqliteStorageProviderService.get();
// clear old cookies
apiService.clearCookies();
// init settings loading
const mindsSettingsPromise = mindsService.getSettings();
// On app login (runs if the user login or if it is already logged in)
......@@ -105,8 +98,7 @@ sessionService.onLogin(async () => {
NavigationService.navigate(sessionService.initialScreen);
// check onboarding progress and navigate if necessary
// commenting this to prevent that the app navigates to onboarding after login
stores.onboarding.getProgress();
stores.onboarding.getProgress(sessionService.initialScreen !== 'OnboardingScreenNew');
// check update
if (Platform.OS !== 'ios' && !GOOGLE_PLAY_STORE) {
......@@ -158,22 +150,31 @@ sessionService.onLogout(() => {
// disable yellow boxes
console.disableYellowBox = true;
type State = {
appState: string
if (
Platform.OS === 'android' &&
UIManager.setLayoutAnimationEnabledExperimental
) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
type Props = {
type State = {
appState: string,
};
}
type Props = {};
/**
* App
*/
export default class App extends Component<Props, State> {
export default
@observer
class App extends Component<Props, State> {
/**
* State
*/
state = {
appState: AppState.currentState || ''
}
appState: AppState.currentState || '',
};
/**
* Handle app state changes
......@@ -206,9 +207,8 @@ export default class App extends Component<Props, State> {
*/
async componentDidMount() {
try {
// load app setting before start
const results = await Promise.all([settingsStore.init(), await Linking.getInitialURL()]);
const results = await Promise.all([settingsStore.init(), Linking.getInitialURL()]);
deepLinkUrl = results[1];
......@@ -229,7 +229,7 @@ export default class App extends Component<Props, State> {
logService.info('[App] session initialized');
}
}
} catch(err) {
} catch (err) {
logService.exception('[App] Error initializing the app', err);
Alert.alert(
'Error',
......@@ -245,7 +245,10 @@ export default class App extends Component<Props, State> {
*/
handlePasswordResetDeepLink() {
try {
if (deepLinkUrl && deeplinkService.cleanUrl(deepLinkUrl).startsWith('forgot-password')) {
if (
deepLinkUrl &&
deeplinkService.cleanUrl(deepLinkUrl).startsWith('forgot-password')
) {
const regex = /;username=(.*);code=(.*)/g;
const params = getMaches(deepLinkUrl.replace(/%3B/g, ';'), regex);
......@@ -255,7 +258,7 @@ export default class App extends Component<Props, State> {
deepLinkUrl = '';
return true;
}
} catch(err) {
} catch (err) {
logService.exception('[App] Error checking for password reset deep link', err);
}
return false;
......@@ -284,24 +287,34 @@ export default class App extends Component<Props, State> {
*/
handleOpenURL = (event) => {
deepLinkUrl = event.url;
if (deepLinkUrl) this.handlePasswordResetDeepLink();
if (deepLinkUrl) {
setTimeout(() => {
deeplinkService.navigate(deepLinkUrl);
deepLinkUrl = '';
}, 100);
this.handlePasswordResetDeepLink();
// the var can be cleaned so we check again
if (deepLinkUrl) {
setTimeout(() => {
deeplinkService.navigate(deepLinkUrl);
deepLinkUrl = '';
}, 100);
}
}
}
};
/**
* Render
*/
render() {
// App not shown until the theme is loaded
if (ThemedStyles.theme === -1) {
return null;
}
const app = (
<Provider key="app" {...stores}>
<ErrorBoundary message="An error occurred" containerStyle={CS.centered}>
<StatusBar barStyle={statusBarStyle} />
<NavigationStack
screenProps={ThemedStyles.theme} // force screen re-render when theme change
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
......@@ -319,15 +332,20 @@ export default class App extends Component<Props, State> {
<BlockchainTransactionModalScreen key="blockchainTransactionModal" blockchainTransaction={ stores.blockchainTransaction } />
);
const tosModal = (
<TosModal user={stores.user} key="tosModal" />
)
const tosModal = <TosModal user={stores.user} key="tosModal" />;
return [ app, keychainModal, blockchainTransactionModal, tosModal];
return [app, keychainModal, blockchainTransactionModal, tosModal];
}
renderNotification = () => {
if (!stores.notifications.last) return null;
return <Notification entity={stores.notifications.last} navigation={NavigationService} />
}
if (!stores.notifications.last) {
return null;
}
return (
<Notification
entity={stores.notifications.last}
navigation={NavigationService}
/>
);
};
}
......@@ -2,7 +2,6 @@ import 'react-native';
import React from 'react';
import { Text, TouchableOpacity } from "react-native";
import { shallow } from 'enzyme';
import featuresService from '../../src/common/services/features.service';
import LoginScreen from '../../src/auth/LoginScreen';
......@@ -17,7 +16,7 @@ import renderer from 'react-test-renderer';
describe('LoginScreen component', () => {
beforeEach(() => {
featuresService.features = {'homepage-december-2019': false};
});
it('should renders correctly', () => {
......
......@@ -3,30 +3,14 @@
exports[`ForgotPassword component should renders correctly 1`] = `
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"flex": 1,
}
}
>
<Text
style={
Array [
Object {
"fontFamily": "Roboto",
"fontSize": 28,
"fontWeight": "bold",
"lineHeight": 44,
},
Object {
"color": "#4F4F50",
},
Object {
"marginTop": 15,
},
Object {
"marginBottom": 15,
},
undefined,
undefined,
undefined,
undefined,
]
}
>
......@@ -35,33 +19,16 @@ exports[`ForgotPassword component should renders correctly 1`] = `
<Text
style={
Array [
Object {
"fontFamily": "Roboto",
"fontSize": 17,
"fontWeight": "500",
"lineHeight": 23,
},
Object {
"color": "#4F4F50",
},
undefined,
Object {
"marginBottom": 15,
},
undefined,
undefined,
undefined,
]
}
>
To request a new password, enter your username
</Text>
<View
style={
Array [
Object {
"marginBottom": 10,
},
]
}
>
<View>
<View
style={
Array [
......@@ -69,9 +36,6 @@ exports[`ForgotPassword component should renders correctly 1`] = `
"flexDirection": "row",
"justifyContent": "space-between",
},
Object {
"marginBottom": 5,
},
]
}
>
......@@ -89,7 +53,9 @@ exports[`ForgotPassword component should renders correctly 1`] = `
Object {
"color": "#AEB0B8",
"fontFamily": "Roboto",
"fontSize": 14,
"fontSize": 16,
"fontWeight": "600",
"marginBottom": 5,
},
]
}
......@@ -109,18 +75,7 @@ exports[`ForgotPassword component should renders correctly 1`] = `
returnKeyType="done"
style={
Array [
Object {
"backgroundColor": "transparent",
"borderColor": "#D8D8D8",
"borderRadius": 2,
"borderWidth": 1,
"color": "#4F4F50",
"fontFamily": "Roboto",
"fontSize": 16,
"height": 50,
"lineHeight": 21,
"padding": 10,
},
undefined,
undefined,
]
}
......@@ -131,13 +86,8 @@ exports[`ForgotPassword component should renders correctly 1`] = `
<View
style={
Array [
Object {
"flexDirection": "row",
"justifyContent": "flex-end",
},
Object {
"marginTop": 20,
},
undefined,
undefined,
]
}
>
......@@ -155,16 +105,13 @@ exports[`ForgotPassword component should renders correctly 1`] = `
style={
Object {
"alignItems": "center",
"backgroundColor": "#5DBAC0",
"borderColor": "#5DBAC0",
"borderRadius": 2,
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"height": 60,
"justifyContent": "center",
"margin": 4,
"marginLeft": 0,
"marginRight": 10,
"opacity": 1,
"padding": 4,
}
......@@ -176,16 +123,12 @@ exports[`ForgotPassword component should renders correctly 1`] = `
Object {
"color": "#4690D6",
},
Object {
"color": "white",
"fontSize": 20,
"fontWeight": "500",
},
undefined,
]
}
>
GO BACK
Go back
</Text>
</View>
......@@ -203,16 +146,13 @@ exports[`ForgotPassword component should renders correctly 1`] = `
style={
Object {
"alignItems": "center",
"backgroundColor": "#5DBAC0",
"borderColor": "#5DBAC0",
"borderRadius": 2,
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"height": 60,
"justifyContent": "center",
"margin": 4,
"marginLeft": 0,
"marginRight": 0,
"opacity": 1,
"padding": 4,
}
......@@ -224,16 +164,12 @@ exports[`ForgotPassword component should renders correctly 1`] = `
Object {
"color": "#4690D6",
},
Object {
"color": "white",
"fontSize": 20,
"fontWeight": "500",
},
undefined,
]
}
>
CONTINUE
Continue
</Text>
</View>
......
......@@ -6,12 +6,8 @@ exports[`ForgotScreen component should renders correctly 1`] = `
style={
Array [
Array [
Object {
"flex": 1,
},
Object {
"backgroundColor": "#F5F5F5",
},
undefined,
undefined,
],
Object {
"paddingBottom": 0,
......@@ -22,41 +18,21 @@ exports[`ForgotScreen component should renders correctly 1`] = `
<View
style={
Array [
Object {
"flex": 1,
},
Object {
"padding": 10,
},
undefined,
undefined,
]
}
>
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Object {
"flex": 1,
}
}
>
<Text
style={
Array [
Object {
"fontFamily": "Roboto",
"fontSize": 28,
"fontWeight": "bold",
"lineHeight": 44,
},
Object {
"color": "#4F4F50",
},
Object {
"marginTop": 15,
},
Object {
"marginBottom": 15,
},
undefined,
undefined,
undefined,
undefined,
]
}
>
......@@ -65,33 +41,16 @@ exports[`ForgotScreen component should renders correctly 1`] = `
<Text
style={
Array [
Object {
"fontFamily": "Roboto",
"fontSize": 17,
"fontWeight": "500",
"lineHeight": 23,
},
Object {
"color": "#4F4F50",
},
undefined,
Object {
"marginBottom": 15,
},
undefined,
undefined,
undefined,
]
}
>
To request a new password, enter your username
</Text>
<View
style={
Array [
Object {
"marginBottom": 10,
},
]
}
>
<View>
<View
style={
Array [
......@@ -99,9 +58,6 @@ exports[`ForgotScreen component should renders correctly 1`] = `
"flexDirection": "row",
"justifyContent": "space-between",
},
Object {
"marginBottom": 5,
},
]
}
>
......@@ -119,7 +75,9 @@ exports[`ForgotScreen component should renders correctly 1`] = `
Object {
"color": "#AEB0B8",
"fontFamily": "Roboto",
"fontSize": 14,
"fontSize": 16,
"fontWeight": "600",
"marginBottom": 5,
},
]
}
......@@ -139,18 +97,7 @@ exports[`ForgotScreen component should renders correctly 1`] = `
returnKeyType="done"
style={
Array [
Object {
"backgroundColor": "transparent",
"borderColor": "#D8D8D8",
"borderRadius": 2,
"borderWidth": 1,
"color": "#4F4F50",
"fontFamily": "Roboto",
"fontSize": 16,
"height": 50,
"lineHeight": 21,
"padding": 10,
},
undefined,
undefined,
]
}
......@@ -161,13 +108,8 @@ exports[`ForgotScreen component should renders correctly 1`] = `
<View
style={
Array [
Object {
"flexDirection": "row",
"justifyContent": "flex-end",
},
Object {
"marginTop": 20,
},
undefined,
undefined,
]
}
>
......@@ -185,16 +127,13 @@ exports[`ForgotScreen component should renders correctly 1`] = `
style={
Object {
"alignItems": "center",
"backgroundColor": "#5DBAC0",
"borderColor": "#5DBAC0",
"borderRadius": 2,
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"height": 60,
"justifyContent": "center",
"margin": 4,
"marginLeft": 0,
"marginRight": 10,
"opacity": 1,
"padding": 4,
}
......@@ -206,16 +145,12 @@ exports[`ForgotScreen component should renders correctly 1`] = `
Object {
"color": "#4690D6",
},
Object {
"color": "white",
"fontSize": 20,
"fontWeight": "500",
},
undefined,
]
}
>
GO BACK
Go back
</Text>
</View>
......@@ -233,16 +168,13 @@ exports[`ForgotScreen component should renders correctly 1`] = `
style={
Object {
"alignItems": "center",
"backgroundColor": "#5DBAC0",
"borderColor": "#5DBAC0",
"borderRadius": 2,
"backgroundColor": "white",
"borderColor": "#4690D6",
"borderRadius": 20,
"borderWidth": 1,
"flexDirection": "row",
"height": 60,
"justifyContent": "center",
"margin": 4,
"marginLeft": 0,
"marginRight": 0,
"opacity": 1,
"padding": 4,
}
......@@ -254,16 +186,12 @@ exports[`ForgotScreen component should renders correctly 1`] = `
Object {
"color": "#4690D6",
},
Object {
"color": "white",
"fontSize": 20,
"fontWeight": "500",
},
undefined,
]
}
>
CONTINUE
Continue
</Text>
</View>
......
......@@ -2,79 +2,69 @@
exports[`LoginScreen component should renders correctly 1`] = `
<View
onLayout={[Function]}
style={
Array [
Array [
undefined,
undefined,
],
Object {
"flex": 1,
"justifyContent": "center",
"paddingBottom": 0,
},
]
}
>
<View
<RCTSafeAreaView
emulateUnlessSupported={true}
style={
Array [
Object {
"flex": 10,
"flexDirection": "row",
"justifyContent": "center",
"paddingBottom": 10,
"paddingHorizontal": 20,
"paddingTop": 20,
},
Object {
"backgroundColor": "#F5F5F5",
},
]
}
>
<View
style={
Array [
Object {
"flex": 1,
},
Object {
"paddingTop": 10,
},
]
}
>
<Image
source={
Object {
"testUri": "../../../src/assets/logos/bulb.png",
}
}
style={
Object {
"alignSelf": "center",
"height": 59.51,
"marginTop": 10,
"width": 34.72,
<RCTScrollView>
<View>
<View
style={
Array [
undefined,
undefined,
]
}
}
/>
<LoginForm
onForgot={[Function]}
onLogin={[Function]}
/>
</View>
</View>
>
<Image
resizeMode="contain"
source={
Object {
"testUri": "../../../src/assets/logos/bulb.png",
}
}
style={
Object {
"alignSelf": "center",
"height": 80,
"marginTop": 10,
"width": 40,
}
}
/>
<LoginForm
onForgot={[Function]}
onLogin={[Function]}
/>
</View>
</View>
</RCTScrollView>
</RCTSafeAreaView>
<View
style={
Array [
Object {
"flex": 2,
"flexDirection": "row",
"justifyContent": "center",
"paddingBottom": 20,
"paddingHorizontal": 20,
"paddingTop": 20,
},
Object {
"backgroundColor": "#EFEFEF",
},
undefined,
undefined,
undefined,
]
}
>
......@@ -96,29 +86,12 @@ exports[`LoginScreen component should renders correctly 1`] = `
}
testID="registerButton"
>
<View
style={
Object {
"alignContent": "center",
"alignItems": "center",
"flex": 1,
"flexDirection": "column",
"justifyContent": "center",
}
}
>
<View>
<Text
style={
Array [
Object {
"fontFamily": "Roboto",
"fontSize": 17,
"fontWeight": "500",
"lineHeight": 23,
},
Object {
"color": "#939397",
},
undefined,
undefined,
]
}
>
......@@ -127,15 +100,8 @@ exports[`LoginScreen component should renders correctly 1`] = `
<Text
style={
Array [
Object {
"fontFamily": "Roboto",
"fontSize": 28,
"fontWeight": "bold",
"lineHeight": 44,
},
Object {
"color": "#4F4F50",
},
undefined,
undefined,
]
}
>
......
......@@ -4,9 +4,7 @@ exports[`RegisterScreen component should renders correctly 1`] = `
<View
style={
Array [
Object {
"flex": 1,
},
undefined,
]
}
>
......
......@@ -280,7 +280,6 @@ exports[`channel header component owner should render correctly 1`] = `
</Text>
</View>
</View>
</View>
<View
accessible={true}
......
......@@ -93,6 +93,7 @@ exports[`channel subscribers component should render correctly 1`] = `
</View>
</View>
<RCTScrollView
ListFooterComponent={null}
data={
Array [
Object {
......
......@@ -7,7 +7,7 @@ import { MINDS_FEATURES } from '../../../src/config/Config';
describe('Feature service', () => {
it('should return features', async () => {
let features = {"crypto": false}
let features = { crypto: false, 'onboarding-december-2019': true };
expect(service.features).toEqual(features);
expect(service.has('crypto')).toEqual(false);
});
......
......@@ -30,7 +30,7 @@
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:windowSoftInputMode="adjustResize"
android:windowSoftInputMode="stateVisible"
android:theme="@style/AppTheme"
android:largeHeap="true">
<activity
......@@ -40,7 +40,7 @@
android:label="@string/app_name"
android:screenOrientation="portrait"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:windowSoftInputMode="stateVisible"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
......
......@@ -26,10 +26,10 @@ org.gradle.jvmargs=-Xmx2048m
systemProp.org.gradle.internal.http.connectionTimeout=180000
systemProp.org.gradle.internal.http.socketTimeout=180000
versionName=3.15.0
versionName=3.15.1
# CUSTOM
versionCode=1050000019
versionCode=1050000020
# PLAY STORE (Keep double hash for the CI)
## versionCode=310037
## versionCode=310039
import deleteUser from './helpers/deleteUser';
import sleep from '../src/common/helpers/sleep';
describe('Register Flow', () => {
describe.skip('Register Flow', () => {
const username = 'e2euser' + ((Math.random() * 0xffffff) << 0).toString(16);
const password = process.env.loginPass;
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.15.0</string>
<string>3.15.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.15.0</string>
<string>3.15.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -19,7 +19,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>3.15.0</string>
<string>3.15.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>3.15.0</string>
<string>3.15.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
......@@ -281,7 +281,7 @@ PODS:
- React
- RNScreens (2.0.0-alpha.11):
- React
- RNSentry (1.0.9):
- RNSentry (1.2.2):
- React
- Sentry (~> 4.4.0)
- RNShare (2.0.0):
......@@ -528,7 +528,7 @@ SPEC CHECKSUMS:
RNLocalize: 07eb7a91d10021cdf59d80061ebf3adb8a5b5688
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
RNScreens: ad3661f864ef18d952e9a4799b6791683e33c1fc
RNSentry: 2803ba8c8129dcf26b79e9b4d8c80168be6e4390
RNSentry: 9cfa3717b1e6bf9ad4b124683e78e3b98b01d3af
RNShare: 8b171d4b43c1d886917fdd303bf7a4b87167b05c
RNSVG: f6177f8d7c095fada7cfee2e4bb7388ba426064c
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
......@@ -537,6 +537,6 @@ SPEC CHECKSUMS:
Sentry: 5d312a04e369154aeac616214f4dfc3cbcc8b296
Yoga: ba3d99dbee6c15ea6bbe3783d1f0cb1ffb79af0f
PODFILE CHECKSUM: 1f47b505eab73a09e6446d8196944df88cfdecc2
PODFILE CHECKSUM: 0d007641cd0de7e8e084380d99fff938fae2d0c4
COCOAPODS: 1.8.4
......@@ -17,7 +17,8 @@
"error":"SORRY, WE COULDN'T LOAD THE ACTIVITY",
"tryAgain":"PLEASE TRY AGAIN LATER",
"remindBlocked":"This remind is not available because you blocked",
"scheduled":"This activity is scheduled to be shown on"
"scheduled":"This activity is scheduled to be shown on",
"pendingModeration":"This post is awaiting moderation"
},
"permissions": {
"externalStorage":"Minds needs access to your external storage so you can upload awesome pictures.",
......@@ -481,7 +482,8 @@
"confirmDeleteKeychain1":"This will delete your Ethereum keychain from this phone. Ensure you backed up the private keys. If you didn't you can lose access to all your funds. There's NO UNDO!",
"pushNotification":"Push Notifications",
"regenerateKey":"Regenerate messenger keys",
"deleteBlockchain":"Delete blockchain keychain"
"deleteBlockchain":"Delete blockchain keychain",
"darkMode":"Dark mode"
},
"comingSoon":{
"try":"Try the canary app at",
......@@ -681,6 +683,12 @@
"update": {
"title":"Update to v{{version}}"
},
"searchBar": {
"searchHistory":"Search History",
"emptySearchHistory":"You don't have search history yet",
"emptySuggested":"We couldn't find what you look for",
"clear": "Clear"
},
"downloading":"Downloading",
"confirm18":"Confirm you are 18+",
"postCantBeShown":"This post can't be shown on apps installed via the Play Store.",
......@@ -692,7 +700,7 @@
"tapCopyError":"Tap to copy the error",
"save":"Save",
"edit":"Edit",
"goback":"GO BACK",
"goback":"Go back",
"copy":"Copy",
"language":"Language",
"categories":"Categories",
......@@ -796,7 +804,7 @@
"notAllowed":"You are not allowed",
"undo":"Undo",
"validate":"VALIDATE",
"tabTitleNewsfeed":"Newsfeed",
"tabTitleNewsfeed":"Home",
"tabTitleNotifications":"Notifications",
"tabTitleDiscovery":"Discovery"
}
......@@ -20,9 +20,9 @@
"scheduled": "Esta actividad esta agendada para mostrarse en"
},
"auth": {
"login": "INGRESAR",
"login": "Ingresar",
"create": "CREAR UN CANAL",
"forgot": "OLVIDÉ MI CLAVE",
"forgot": "Olvidé mi clave",
"email": "Email",
"username": "Usuario",
"password": "Clave",
......
{
"versions": [
{
"version": "3.15.0",
"href": "https://cdn-assets.minds.com/mobile/Minds-stable-3-15-0.apk",
"sourceHref": "https://gitlab.com/minds/mobile-native/commits/v3.15.0",
"changelog": [
"New login and register screens",
"Bug fixes"
],
"unstable": false,
"hashes": [
{
"type": "md5",
"value": "0b41d23037645b7f3eb033c1f617da6a"
},
{
"type": "sha256",
"value": "f069afcaef9527921fff54194734b62da77756610228a7590a98e29d6f159462"
},
{
"type": "sha512",
"value": "0e6606c99855e173ddc1e62d716684a920e7cec4433bdce8495e0216fa3c0e61cb5ff1abd9dd5cb265764f81bfb6518a37764a645f02350083cc44cb19c60f43"
}
]
},
{
"version": "3.14.0",
"timestamp": 1576783681,
......
......@@ -3,12 +3,11 @@ import React, { PureComponent } from 'react';
import { View, Text, SafeAreaView } from 'react-native';
import authService from './AuthService';
import { CommonStyle as CS } from '../styles/Common';
import { ComponentsStyle } from '../styles/Components';
import i18n from '../common/services/i18n.service';
import logService from '../common/services/log.service';
import Button from '../common/components/Button';
import Input from '../common/components/Input';
import ThemedStyles from '../styles/ThemedStyles';
/**
* Forgot Password Form
......@@ -31,10 +30,27 @@ export default class ForgotPassword extends PureComponent {
* Render
*/
render() {
const CS = ThemedStyles.style;
return (
<SafeAreaView style={CS.flexContainer}>
<Text style={[CS.titleText, CS.colorPrimaryText, CS.marginTop3x, CS.marginBottom3x]}>{i18n.t('auth.forgot')}</Text>
<Text style={[CS.subTitleText, CS.colorPrimaryText, CS.marginTop1x, CS.marginBottom3x]}>{this.state.msg}</Text>
<Text
style={[
CS.titleText,
CS.colorPrimaryText,
CS.marginTop3x,
CS.marginBottom3x,
]}>
{i18n.t('auth.forgot')}
</Text>
<Text
style={[
CS.subTitleText,
CS.colorPrimaryText,
CS.marginTop1x,
CS.marginBottom3x,
]}>
{this.state.msg}
</Text>
{!this.state.sent && <Input
placeholder={i18n.t('auth.username')}
returnKeyType={'done'}
......@@ -46,17 +62,17 @@ export default class ForgotPassword extends PureComponent {
<Button
onPress={() => this.onPressBack()}
text={i18n.t('goback')}
containerStyle={[ComponentsStyle.loginButtonNew, CS.marginRight2x]}
textStyle={ComponentsStyle.loginButtonTextNew}
containerStyle={[CS.button, CS.marginRight2x]}
textStyle={CS.buttonText}
/>
{!this.state.sent && <Button
onPress={() => this.onContinuePress()}
text={i18n.t('continue').toUpperCase()}
text={i18n.t('continue')}
loading={this.state.sending}
loadingRight={true}
disable={this.state.sending || this.state.sent}
containerStyle={ComponentsStyle.loginButtonNew}
textStyle={ComponentsStyle.loginButtonTextNew}
containerStyle={CS.button}
textStyle={CS.buttonText}
/>}
</View>
</SafeAreaView>
......@@ -89,4 +105,4 @@ export default class ForgotPassword extends PureComponent {
}
}
}
}
\ No newline at end of file
}
import React, {
Component
} from 'react';
import React, { Component } from 'react';
import {
View,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import { View, KeyboardAvoidingView, Platform } from 'react-native';
import ForgotPassword from './ForgotPassword';
import ResetPassword from './ResetPassword';
import { CommonStyle as CS } from '../styles/Common';
import ThemedStyles from '../styles/ThemedStyles';
/**
* Forgot screen
......@@ -31,9 +24,10 @@ export default class ForgotScreen extends Component {
const code =
this.props.navigation.state.params &&
this.props.navigation.state.params.code;
const CS = ThemedStyles.style;
return (
<KeyboardAvoidingView style={[CS.flexContainer, CS.backgroundThemePrimary]} behavior={ Platform.OS == 'ios' ? 'padding' : null }>
<KeyboardAvoidingView style={[CS.flexContainer, CS.backgroundPrimary]} behavior={ Platform.OS == 'ios' ? 'padding' : null }>
<View style={[CS.flexContainer, CS.padding2x]}>
{code ? <ResetPassword
onBack={this.onForgotBack}
......
import React, {
Component
Component,
} from 'react';
import * as Animatable from 'react-native-animatable';
......@@ -7,20 +7,19 @@ import * as Animatable from 'react-native-animatable';
import {
View,
Text,
ScrollView,
LayoutAnimation,
// TextInput,
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import authService from './AuthService';
import { CommonStyle as CS } from '../styles/Common';
import { ComponentsStyle } from '../styles/Components';
import i18n from '../common/services/i18n.service';
import logService from '../common/services/log.service';
import Input from '../common/components/Input';
import Button from '../common/components/Button';
import ThemedStyles from '../styles/ThemedStyles';
/**
* Login Form
......@@ -52,59 +51,62 @@ export default class LoginForm extends Component {
* Render
*/
render() {
const CS = ThemedStyles.style;
const msg = this.state.msg ? (
<Animatable.Text animation="bounceInLeft" style={[CS.subTitleText, CS.colorSecondaryText, { textAlign: 'center' }]} testID="loginMsg">{this.state.msg}</Animatable.Text>
<Animatable.Text
animation="bounceInLeft"
useNativeDriver
style={[CS.subTitleText, CS.colorSecondaryText, CS.textCenter]}
testID="loginMsg">
{this.state.msg}
</Animatable.Text>
) : null;
return (
<View
style={[CS.flexContainer]}>
<ScrollView style={[CS.flexContainer]}>
<View style={{flex:6}}>
<Text style={[CS.titleText, CS.colorPrimaryText]}>
{i18n.t('auth.login')}
</Text>
{msg}
<View style={[CS.flexContainer, CS.marginTop6x]}>
<Text style={[CS.titleText, CS.colorPrimaryText, CS.marginBottom2x]}>
{i18n.t('auth.login')}
</Text>
{msg}
<Input
placeholder={i18n.t('auth.username')}
onChangeText={this.setUsername}
value={this.state.username}
style={CS.marginBottom2x}
testID="usernameInput"
/>
<View>
<Input
placeholder={i18n.t('auth.username')}
onChangeText={this.setUsername}
value={this.state.username}
testID="usernameInput"
placeholder={i18n.t('auth.password')}
secureTextEntry={this.state.hidePassword}
onChangeText={this.setPassword}
value={this.state.password}
style={CS.marginBottom2x}
testID="userPasswordInput"
/>
<View>
<Input
placeholder={i18n.t('auth.password')}
secureTextEntry={this.state.hidePassword}
onChangeText={this.setPassword}
value={this.state.password}
testID="userPasswordInput"
/>
<Icon
name={this.state.hidePassword ? 'md-eye' : 'md-eye-off'}
size={25}
onPress={this.toggleHidePassword}
style={ComponentsStyle.loginInputIconNew}
/>
</View>
</View>
<View style={[{flex:6, marginTop: 30}]}>
<Button
onPress={() => this.onLoginPress()}
text={i18n.t('auth.login')}
containerStyle={ComponentsStyle.loginButtonNew}
textStyle={ComponentsStyle.loginButtonTextNew}
key={1}
loading={this.state.inProgress}
loadingRight={true}
disabled={this.state.inProgress}
disabledStyle={CS.backgroundTransparent}
testID="loginButton"
<Icon
name={this.state.hidePassword ? 'md-eye' : 'md-eye-off'}
size={25}
onPress={this.toggleHidePassword}
style={CS.inputIcon}
/>
<View style={CS.marginTop4x}>
<Text style={[ComponentsStyle.linkNew]} onPress={this.onForgotPress}>{i18n.t('auth.forgot')}</Text>
</View>
</View>
</ScrollView>
<Button
onPress={() => this.onLoginPress()}
text={i18n.t('auth.login')}
containerStyle={CS.button}
textStyle={CS.buttonText}
key={1}
loading={this.state.inProgress}
loadingRight={true}
disabled={this.state.inProgress}
disabledStyle={CS.backgroundTransparent}
testID="loginButton"
/>
<View style={CS.marginTop4x}>
<Text style={[CS.link, CS.fontL]} onPress={this.onForgotPress}>{i18n.t('auth.forgot')}</Text>
</View>
</View>
);
}
......@@ -193,6 +195,7 @@ export default class LoginForm extends Component {
this.props.onLogin();
})
.catch(errJson => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
if (errJson.error === 'invalid_grant' || errJson.error === 'invalid_client') {
this.setState({ msg: i18n.t('auth.invalidGrant'), inProgress: false });
return;
......
......@@ -6,19 +6,22 @@ import {
Keyboard,
Animated,
Text,
Image,
TouchableOpacity,
SafeAreaView,
ScrollView,
Platform,
KeyboardAvoidingView,
} from 'react-native';
import LoginForm from './LoginForm';
import { CommonStyle as CS } from '../styles/Common';
import logService from '../common/services/log.service';
import featuresService from '../common/services/features.service';
import i18nService from '../common/services/i18n.service';
import sessionService from '../common/services/session.service';
const LOGO_HEIGHT = 100;
const LOGO_HEIGHT_SMALL = 50;
import ThemedStyles from '../styles/ThemedStyles';
const LOGO_HEIGHT = 80;
const LOGO_HEIGHT_SMALL = 40;
/**
* Login screen
......@@ -31,20 +34,21 @@ export default class LoginScreen extends Component {
header: null
}
state = {
keyboard: false,
}
constructor(props) {
super(props);
this.logoHeight = new Animated.Value(LOGO_HEIGHT);
}
componentDidMount() {
// Setting this here because if user register, then onboarding then logout and login again, will go to onboarding again
sessionService.setInitialScreen('Tabs');
this.setState({ loading:true });
}
componentWillMount () {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
this.keyboardWillShowSub = Keyboard.addListener('keyboardDidShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardDidHide', this.keyboardWillHide);
}
componentWillUnmount() {
......@@ -54,59 +58,56 @@ export default class LoginScreen extends Component {
keyboardWillShow = (event) => {
Animated.timing(this.logoHeight, {
duration: event.duration,
duration: 500,
toValue: LOGO_HEIGHT_SMALL,
}).start();
// this.setState({keyboard: true});
// LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
};
keyboardWillHide = (event) => {
Animated.timing(this.logoHeight, {
duration: event.duration,
duration: 500,
toValue: LOGO_HEIGHT,
}).start();
};
getLoginBody = () => {
return (
<View style={[CS.flexContainer, CS.paddingTop2x]}>
<Image
source={require('./../assets/logos/bulb.png')}
style={styles.bulb}
/>
<LoginForm
onLogin={() => this.login()}
onForgot={this.onPressForgot}
/>
</View>
);
};
getLoginFooter = () => {
return (
<TouchableOpacity onPress={this.onPressRegister} testID="registerButton">
<View style={CS.flexColumnCentered}>
<Text style={[CS.subTitleText, CS.colorSecondaryText]}>{i18nService.t('auth.haveAccount')}</Text>
<Text style={[CS.titleText, CS.colorPrimaryText]}>{i18nService.t('auth.createChannel')}</Text>
</View>
</TouchableOpacity>
);
// this.setState({keyboard: false});
// LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
};
/**
* Render
*/
render() {
const resizeMode = 'center';
const CS = ThemedStyles.style;
return (
<View style={[CS.flexContainerCenter]}>
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
{this.getLoginBody()}
<KeyboardAvoidingView style={[CS.flexColumnStretch, CS.backgroundPrimary]} behavior={Platform.OS == 'ios' ? 'padding' : null} >
<SafeAreaView style={[styles.flex10]}>
<ScrollView style={CS.flexContainer}>
<View style={[CS.paddingHorizontal4x, CS.flexColumnStretch]}>
<Animated.Image
resizeMode="contain"
source={require('./../assets/logos/bulb.png')}
style={[styles.bulb, { height: this.logoHeight }]}
/>
<LoginForm
onLogin={() => this.login()}
onForgot={this.onPressForgot}
/>
</View>
</ScrollView>
</SafeAreaView>
<View style={[CS.paddingVertical2x, CS.backgroundSecondary, CS.mindsLayoutFooter]}>
<TouchableOpacity onPress={this.onPressRegister} testID="registerButton">
<View style={CS.flexColumnCentered}>
<Text style={[CS.subTitleText, CS.colorSecondaryText]}>{i18nService.t('auth.haveAccount')}</Text>
<Text style={[CS.titleText, CS.colorPrimaryText]}>{i18nService.t('auth.createChannel')}</Text>
</View>
</TouchableOpacity>
</View>
<View style={[CS.mindsLayoutFooter, CS.backgroundThemeSecondary]}>
{this.getLoginFooter()}
</View>
</View>
</KeyboardAvoidingView>
);
}
......@@ -133,15 +134,12 @@ export default class LoginScreen extends Component {
}
const styles = StyleSheet.create({
logo: {
width: 200,
height: 84,
marginBottom: 30,
alignSelf: 'center',
flex10: {
flex: 10,
},
bulb: {
width: 34.72,
height: 59.51,
width: 40,
height: 67,
alignSelf: 'center',
marginTop: 10
},
......
......@@ -4,19 +4,15 @@ import React, {
import {
Text,
TextInput,
KeyboardAvoidingView,
View,
ScrollView,
Linking,
Alert,
StyleSheet,
TouchableOpacity
TouchableOpacity,
SafeAreaView
} from 'react-native';
import authService from '../auth/AuthService';
import { CommonStyle as CS} from '../styles/Common';
import { ComponentsStyle } from '../styles/Components';
import { observer, inject } from 'mobx-react/native';
......@@ -30,13 +26,15 @@ import Input from '../common/components/Input';
import Icon from 'react-native-vector-icons/MaterialIcons';
import Button from '../common/components/Button';
import { DISABLE_PASSWORD_INPUTS } from '../config/Config';
import ThemedStyles from '../styles/ThemedStyles';
/**
* Register Form
*/
export default
@inject('user')
@observer
export default class RegisterForm extends Component {
class RegisterForm extends Component {
state = {
error: {},
password: '',
......@@ -74,106 +72,100 @@ export default class RegisterForm extends Component {
setConfirmPassword = confirmPassword => this.setState({confirmPassword});
getFormBody = () => {
const CS = ThemedStyles.style;
return (
<ScrollView style={[CS.flexContainer, CS.marginTop2x]}>
<View style={CS.marginBottom3x}>
<TouchableOpacity onPress={this.props.onBack}>
<ScrollView style={[CS.flexContainer, CS.marginTop2x]} contentContainerStyle={CS.paddingHorizontal4x}>
<SafeAreaView style={CS.flexContainer}>
<TouchableOpacity onPress={this.props.onBack} style={CS.marginBottom3x}>
<Icon size={34} name="keyboard-arrow-left" style={CS.colorSecondaryText} />
</TouchableOpacity>
</View>
<View style={[CS.marginBottom3x, CS.centered]}>
<Text style={[CS.titleText, CS.colorPrimaryText]}>
<Text style={[CS.marginBottom3x, CS.textCenter, CS.titleText, CS.colorPrimaryText]}>
{i18n.t('auth.join')}
</Text>
</View>
<View>
<Text style={{color: '#F00', textAlign: 'center', paddingTop:4, paddingLeft:4}}>
{this.state.error.termsAcceptedError}
</Text>
</View>
<Input
placeholder={i18n.t('auth.username')}
onChangeText={this.setUsername}
value={this.state.username}
editable={!this.state.inProgress}
testID="registerUsernameInput"
/>
<Input
placeholder={i18n.t('auth.email')}
onChangeText={this.setEmail}
value={this.state.email}
editable={!this.state.inProgress}
testID="registerEmailInput"
/>
<Input
placeholder={i18n.t('auth.password')}
secureTextEntry={!DISABLE_PASSWORD_INPUTS} // e2e workaround
onChangeText={this.setPassword}
value={this.state.password}
editable={!this.state.inProgress}
testID="registerPasswordInput"
/>
{ this.state.password ?
<Input
placeholder={i18n.t('auth.confirmpassword')}
style={CS.marginBottom2x}
placeholder={i18n.t('auth.username')}
onChangeText={this.setUsername}
value={this.state.username}
editable={!this.state.inProgress}
testID="registerUsernameInput"
/>
<Input
style={CS.marginBottom2x}
placeholder={i18n.t('auth.email')}
onChangeText={this.setEmail}
value={this.state.email}
editable={!this.state.inProgress}
testID="registerEmailInput"
/>
<Input
style={CS.marginBottom2x}
placeholder={i18n.t('auth.password')}
secureTextEntry={!DISABLE_PASSWORD_INPUTS} // e2e workaround
onChangeText={this.setConfirmPassword}
value={this.state.confirmPassword}
onChangeText={this.setPassword}
value={this.state.password}
editable={!this.state.inProgress}
testID="registerPasswordConfirmInput"
/> : null }
<CheckBox
right
iconLeft
containerStyle={ComponentsStyle.registerCheckboxNew}
title={<Text style={ComponentsStyle.termsNew}>{i18n.t('auth.accept')} <Text style={ComponentsStyle.linkNew} onPress={ ()=> Linking.openURL('https://www.minds.com/p/terms') }>{i18n.t('auth.termsAndConditions')}</Text></Text>}
checked={this.state.termsAccepted}
textStyle={ComponentsStyle.registerCheckboxTextNew}
onPress={() => { this.setState({ termsAccepted: !this.state.termsAccepted }) }}
disabled={this.state.inProgress}
testID="checkbox"
/>
testID="registerPasswordInput"
/>
{ this.state.password ?
<Input
placeholder={i18n.t('auth.confirmpassword')}
secureTextEntry={!DISABLE_PASSWORD_INPUTS} // e2e workaround
onChangeText={this.setConfirmPassword}
value={this.state.confirmPassword}
editable={!this.state.inProgress}
testID="registerPasswordConfirmInput"
/> : null }
<CheckBox
containerStyle={CS.checkbox}
title={<Text style={[CS.colorSecondaryText, CS.fontL]}>{i18n.t('auth.accept')} <Text style={CS.link} onPress={ ()=> Linking.openURL('https://www.minds.com/p/terms') }>{i18n.t('auth.termsAndConditions')}</Text></Text>}
checked={this.state.termsAccepted}
onPress={this.check}
disabled={this.state.inProgress}
testID="checkbox"
/>
<View style={CS.flexContainer, CS.paddingTop2x}>
<Button
onPress={() => this.onPressRegister()}
borderRadius={2}
containerStyle={CS.button}
textStyle={CS.buttonText}
loading={this.state.inProgress}
loadingRight={true}
disabled={this.state.inProgress}
text={i18n.t('auth.createChannel')}
testID="registerCreateButton"
/>
<Text style={[CS.subTitleText, CS.colorSecondaryText, CS.centered, CS.marginTop2x]}>
{i18n.to('auth.alreadyHaveAccount', null, {
login: (
<Text style={[CS.link, CS.fontL]} onPress={this.props.onBack}>
{i18n.t('auth.login')}
</Text>
),
})}
</Text>
</View>
</SafeAreaView>
</ScrollView>
);
};
getFormFooter = () => {
return (
<View style={CS.flexContainer}>
<Button
onPress={() => this.onPressRegister()}
borderRadius={2}
containerStyle={ComponentsStyle.loginButtonNew}
loading={this.state.inProgress}
loadingRight={true}
disabled={this.state.inProgress}
text={''}
testID="registerCreateButton"
>
<Text style={ComponentsStyle.loginButtonTextNew}>{i18n.t('auth.createChannel')}</Text>
</Button>
<Text style={[CS.subTitleText, CS.colorSecondaryText, CS.centered, CS.marginTop2x]}>
{i18n.to('auth.alreadyHaveAccount', null, {
login: (
<Text style={[ComponentsStyle.linkNew, CS.fontL]} onPress={this.props.onBack}>
{i18n.t('auth.login')}
</Text>
),
})}
</Text>
</View>
);
check = () => {
this.setState({ termsAccepted: !this.state.termsAccepted })
};
render() {
const CS = ThemedStyles.style;
return (
<View style={[CS.flexContainerCenter]}>
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
{this.getFormBody()}
</View>
<View style={[CS.mindsLayoutFooter, CS.backgroundThemePrimary]}>
{this.getFormFooter()}
</View>
<View style={[CS.flexContainerCenter, CS.backgroundPrimary]}>
{this.getFormBody()}
</View>
);
}
......
......@@ -3,8 +3,8 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import RegisterForm from './RegisterForm';
import { CommonStyle } from '../styles/Common';
import logService from '../common/services/log.service';
import ThemedStyles from '../styles/ThemedStyles';
/**
* Register screen
......@@ -22,7 +22,7 @@ export default class RegisterScreen extends Component {
*/
render() {
return (
<View style={[CommonStyle.flexContainer]}>
<View style={[ThemedStyles.style.flexContainer]}>
<RegisterForm onRegister={this.onRegister} onBack={this.onPressBack} />
</View>
);
......
......@@ -8,6 +8,7 @@ import sessionService from '../common/services/session.service';
import UserModel from './../channel/UserModel';
import { MINDS_FEATURES } from '../config/Config';
import { Alert } from 'react-native';
import searchBarService from '../topbar/SearchBar.service';
/**
* Login Store
......@@ -72,6 +73,10 @@ class UserStore {
//}
this.setUser(response.channel);
// Load search history
searchBarService.init(this.me.guid)
if (this.me.canCrypto) {
MINDS_FEATURES.crypto = true;
}
......@@ -93,6 +98,36 @@ class UserStore {
this.searching = !this.searching;
}
/**
* Call onItemTap
*/
searchBarItemTap(item) {
searchBarService.onItemTap(item.username);
}
/**
* Clear search history for user
*/
searchBarClearHistory() {
searchBarService.clearSearchHistory();
}
/**
* Get user search history
*/
async getSearchHistory() {
return await searchBarService.getSearchHistory();
}
/**
* Get suggested Search
* @param {String} search
*/
async getSuggestedSearch(search) {
return await searchBarService.getSuggestedSearch(search)
}
}
export default UserStore;
......@@ -27,7 +27,7 @@ export default class CaptureFab extends Component {
}
render() {
if (featuresService.has('new-navigation-january-2020')) {
if (featuresService.has('navigation-2020')) {
return null;
}
......
......@@ -134,22 +134,22 @@ class ChannelService {
});
}
getSubscribers(guid, filter, offset) {
const tag = `channel:subscribers:${guid}`;
// abort previous call
abort(tag);
return api.get('api/v1/subscribe/' + filter + '/' + guid, { offset: offset, limit: 12 }, tag)
.then((data) => {
return {
entities: data.users,
offset: data['load-next'],
}
})
.catch(err => {
logService.exception('[ChannelService]', err);
throw new Error (i18n.t('errorMessage'));
})
/**
* Get subscribers
* @param {string} guid
* @param {string} filter
* @param {string} offset
*/
async getSubscribers(guid, filter, offset) {
const data = await api.get('api/v1/subscribe/' + filter + '/' + guid, {
offset: offset,
limit: 12,
});
return {
entities: data.users,
offset: data['load-next'],
};
}
async getScheduledCount(guid) {
......
......@@ -46,10 +46,10 @@ const TouchableCustom = withPreventDoubleTap(Touchable);
/**
* Channel Header
*/
export default
@inject('user', 'onboarding')
@observer
export default class ChannelHeader extends Component {
class ChannelHeader extends Component {
ActionSheetRef;
loaded;
......@@ -304,12 +304,12 @@ export default class ChannelHeader extends Component {
</ReadMore>
</View>
}
{!isEditable && channel.city &&
{!isEditable && channel.city ? (
<View style={[CommonStyle.paddingTop2x, CommonStyle.flexContainer, CommonStyle.rowJustifyStart]}>
<Icon name="md-pin" size={24} style={styles.name} />
<Icon name="md-pin" size={24} style={styles.name} />
<Text style={CommonStyle.marginLeft1x}>{channel.city}</Text>
</View>
}
) : null}
</View>
<TouchableCustom onPress={this.changeAvatarAction} style={styles.avatar} disabled={!isEditable}>
......
......@@ -25,7 +25,9 @@ export default class CompleteProfile extends Component {
* Render
*/
render() {
let onboarding = featuresService.has('onboarding-december-2019') ? 'OnboardingScreenNew' : 'OnboardingScreen';
let onboarding = featuresService.has('onboarding-december-2019')
? 'OnboardingScreenNew'
: 'OnboardingScreen';
return (
<TouchableOpacity style={[CS.padding2x]} onPress={() => navigationService.push(onboarding)}>
<View>
......
......@@ -27,14 +27,15 @@ import DiscoveryUser from '../../discovery/DiscoveryUser';
import CenteredLoading from '../../common/components/CenteredLoading';
import { CommonStyle } from '../../styles/Common';
import colors from '../../styles/Colors';
import ErrorLoading from '../../common/components/ErrorLoading';
/**
* Discovery screen
*/
export default
@inject('channelSubscribersStore')
@observer
export default class ChannelSubscribers extends Component {
class ChannelSubscribers extends Component {
/**
* On component will mount
......@@ -66,35 +67,40 @@ export default class ChannelSubscribers extends Component {
render() {
let body;
const channels = this.props.channelSubscribersStore;
const store = this.props.channelSubscribersStore;
const footerCmp = store.errorLoading ? (
<ErrorLoading message={i18n.t('cantLoad')} tryAgain={store.loadList} />
) : null;
if (!channels.list.loaded && !channels.list.refreshing) {
body = <CenteredLoading />
if (!store.list.loaded && !store.list.refreshing && !store.errorLoading) {
body = <CenteredLoading />;
} else {
body = (
<FlatList
data={channels.list.entities.slice()}
data={store.list.entities.slice()}
renderItem={this.renderRow}
keyExtractor={item => item.guid}
onRefresh={this.refresh}
refreshing={channels.list.refreshing}
refreshing={store.list.refreshing}
onEndReached={this.loadFeed}
// onEndReachedThreshold={0}
initialNumToRender={12}
style={styles.listView}
removeClippedSubviews={false}
ListFooterComponent={footerCmp}
/>
)
);
}
return (
<View style={CommonStyle.flexContainer}>
<View style={styles.topbar}>
<View style={[CommonStyle.flexContainer, CommonStyle.rowJustifyCenter]}>
<TouchableHighlight underlayColor='transparent' onPress={() => channels.setFilter('subscribers')} style={channels.filter == 'subscribers'? [styles.selectedButton, CommonStyle.flexContainerCenter]: [styles.buttons, CommonStyle.flexContainerCenter]}>
<TouchableHighlight underlayColor='transparent' onPress={() => store.setFilter('subscribers')} style={store.filter == 'subscribers'? [styles.selectedButton, CommonStyle.flexContainerCenter]: [styles.buttons, CommonStyle.flexContainerCenter]}>
<Text>{i18n.t('subscribers')}</Text>
</TouchableHighlight>
<TouchableHighlight underlayColor='transparent' onPress={() => channels.setFilter('subscriptions')} style={channels.filter == 'subscriptions'? [styles.selectedButton, CommonStyle.flexContainerCenter]: [styles.buttons, CommonStyle.flexContainerCenter ]}>
<TouchableHighlight underlayColor='transparent' onPress={() => store.setFilter('subscriptions')} style={store.filter == 'subscriptions'? [styles.selectedButton, CommonStyle.flexContainerCenter]: [styles.buttons, CommonStyle.flexContainerCenter ]}>
<Text>{i18n.t('subscriptions')}</Text>
</TouchableHighlight>
</View>
......@@ -104,7 +110,6 @@ export default class ChannelSubscribers extends Component {
);
}
/**
* Load subs data
*/
......
import {
observable,
action
} from 'mobx'
import { observable, action } from 'mobx';
import OffsetListStore from '../../common/stores/OffsetListStore';
import channelService from '../ChannelService';
import UserModel from '../UserModel';
import logService from '../../common/services/log.service';
/**
* Subscribers Store
*/
class ChannelSubscribersStore {
list = new OffsetListStore();
@observable errorLoading = false;
@observable loading = false;
@observable filter = 'subscribers';
guid = '';
loading = false;
setGuid(guid) {
let reload = (this.guid != guid);
let reload = this.guid != guid;
this.guid = guid;
this.loadList(reload);
}
/**
* Load boost list
* Set action
*/
loadList(reload = false) {
@action
setLoading(value: boolean) {
this.loading = value;
}
/**
* Set the error loading flag
* @param {boolean} value
*/
@action
setErrorLoading(value: boolean) {
this.errorLoading = value;
}
/**
* Load boost list
*/
loadList = async (reload = false) => {
if (this.list.cantLoadMore()) {
return Promise.resolve();
}
if(reload)
if (reload) {
this.list.clearList();
}
this.loading = true;
return channelService.getSubscribers(this.guid, this.filter, this.list.offset)
.then( feed => {
feed.entities = UserModel.createMany(feed.entities);
this.list.setList(feed);
})
.finally(() => {
this.loading = false;
});
}
try {
this.setLoading(true);
this.setErrorLoading(false);
const feed = await channelService.getSubscribers(
this.guid,
this.filter,
this.list.offset,
);
feed.entities = UserModel.createMany(feed.entities);
this.list.setList(feed);
} catch (err) {
this.setErrorLoading(true);
logService.exception(err);
} finally {
this.setLoading(false);
}
};
@action
reset() {
......@@ -62,10 +84,9 @@ class ChannelSubscribersStore {
*/
refresh() {
this.list.refresh();
this.loadList()
.finally(() => {
this.list.refreshDone();
});
this.loadList().finally(() => {
this.list.refreshDone();
});
}
@action
......@@ -77,4 +98,4 @@ class ChannelSubscribersStore {
}
}
export default ChannelSubscribersStore;
\ No newline at end of file
export default ChannelSubscribersStore;
......@@ -191,9 +191,11 @@ class CommentList extends React.Component<PropsType, StateType> {
*/
scrollToBottom = () => {
setTimeout(() => {
this.listRef.scrollToEnd();
}, 250); //delay to allow rendering
}
if (this.listRef) {
this.listRef.scrollToEnd();
}
}, 600); //delay to allow rendering
};
/**
* Set comment text
......@@ -425,8 +427,10 @@ class CommentList extends React.Component<PropsType, StateType> {
}, 1000);
} else {
if (this.listRef && this.listRef._listRef) {
const frame = this.listRef._listRef._getFrameMetricsApprox(index);
this.onCommentFocus(comment, frame.offset + frame.length);
setTimeout(() => {
const frame = this.listRef._listRef._getFrameMetricsApprox(index);
this.onCommentFocus(comment, frame.offset + frame.length);
}, 1000);
}
}
}
......
......@@ -359,6 +359,13 @@ export default class BaseModel {
return this.time_created * 1000 > Date.now();
}
/**
* Check if awaiting for moderation
*/
isPending() {
return this.pending && this.pending !== '0'; // asking like this because front does the same
}
static isScheduled(timeCreatedValue) {
let response = false;
......
import React, {Component} from 'react';
import {StyleSheet, Text} from 'react-native';
import IconMC from 'react-native-vector-icons/MaterialCommunityIcons';
import {Tooltip} from 'react-native-elements';
import { ScrollView } from 'react-native-gesture-handler';
// workaround for android
import Tooltip from "rne-modal-tooltip";
export default class InfoPopup extends Component {
render() {
......
import React, { Component } from 'react';
import { Text, View, StyleSheet, TouchableOpacity } from 'react-native';
import { ComponentsStyle } from '../../styles/Components';
import { CommonStyle as CS } from '../../styles/Common';
import DateTimePicker from 'react-native-modal-datetime-picker';
import InfoPopup from './InfoPopup';
import PhoneValidationComponent from './PhoneValidationComponent';
import TextInput from './TextInput';
import ThemedStyles from '../../styles/ThemedStyles';
/**
* Form input
......@@ -45,10 +45,14 @@ export default class Input extends Component {
* Text input
*/
textInput = () => {
const CS = ThemedStyles.style;
return (
<TextInput
{...this.props}
style={[ComponentsStyle.loginInputNew, this.props.style]}
style={[
CS.input,
this.props.style,
]}
placeholderTextColor="#444"
returnKeyType={'done'}
autoCapitalize={'none'}
......@@ -62,10 +66,11 @@ export default class Input extends Component {
* Phone input
*/
phoneInput = () => {
const CS = ThemedStyles.style;
return (
<PhoneValidationComponent
style={[ComponentsStyle.loginInputNew, this.props.style]}
textStyle={CS.colorWhite}
style={[CS.input, this.props.style]}
textStyle={CS.colorPrimaryText}
onFocus={this.props.onFocus}
onBlur={this.props.onBlur}
/>
......@@ -76,11 +81,12 @@ export default class Input extends Component {
* Date input
*/
dateInput = () => {
const CS = ThemedStyles.style;
return (
<View>
<TouchableOpacity
{...this.props}
style={[ComponentsStyle.loginInputNew, this.props.style]}
style={[CS.input, this.props.style]}
placeholderTextColor="#444"
returnKeyType={'done'}
autoCapitalize={'none'}
......@@ -122,16 +128,19 @@ export default class Input extends Component {
* Render
*/
render() {
const optional = (<Text style={[styles.optional]}>{"Optional"}</Text>);
const CS = ThemedStyles.style;
const optional = this.props.optional ? (
<Text style={[styles.optional]}>{'Optional'}</Text>
) : null;
return (
<View style={[CS.marginBottom2x]}>
<View style={[styles.row, CS.marginBottom]}>
<View style={CS.marginBottom2x}>
<View style={[styles.row]}>
<View style={styles.row}>
<Text style={[styles.label]}>{this.props.placeholder}</Text>
{this.props.info && <InfoPopup info={this.props.info} />}
</View>
{this.props.optional && optional}
{optional}
</View>
{this.renderInput()}
</View>
......@@ -146,7 +155,9 @@ const styles = StyleSheet.create({
},
label: {
color: '#AEB0B8',
fontSize: 14,
fontSize: 16,
fontWeight: '600',
marginBottom:5 ,
fontFamily: 'Roboto',
},
optional: {
......
......@@ -34,10 +34,6 @@ class FeaturesService {
this.features = features;
// set this to true as per request for build test
// TODO: remove this...
this.features['onboarding-december-2019'] = true;
this.loaded = true;
}
......
......@@ -39,6 +39,8 @@ export const CODE_PUSH_TOKEN = '';
*/
export const MINDS_FEATURES = {
crypto: Platform.OS === 'ios' ? false : true,
'onboarding-december-2019': true,
'navigation-2020': true,
};
/**
......
......@@ -74,7 +74,7 @@ class DiscoveryScreen extends Component {
static navigationOptions = {
tabBarIcon: ({ tintColor }) => (
featuresService.has('new-navigation-january-2020')
featuresService.has('navigation-2020')
? <TabIcon name="hashtag" color={tintColor} />
: <Icon name="search" size={24} color={tintColor} />
),
......
......@@ -55,6 +55,9 @@ class DiscoveryUser extends Component {
*/
_navToChannel = () => {
Keyboard.dismiss();
if (this.props.onUserTap) {
this.props.onUserTap(this.props.row.item);
}
if (this.props.navigation) {
if (this.props.row.item.isOpen() && !this.props.row.item.can(FLAG_VIEW, true)) {
return;
......@@ -87,8 +90,8 @@ class DiscoveryUser extends Component {
* Render
*/
render() {
const {row, ...otherProps} = this.props;
const {row, subscribe, ...otherProps} = this.props;
const renderRightButton = !(subscribe === false);
return (
<TouchableOpacity style={styles.row} onPress={this._navToChannel} {...otherProps}>
<Image source={this.state.source} style={styles.avatar} />
......@@ -96,7 +99,7 @@ class DiscoveryUser extends Component {
<Text style={[styles.body, styles.title, CS.colorPrimaryText]}>{row.item.name}</Text>
<Text style={[styles.body, styles.subtitle, CS.colorSecondaryText]}>@{row.item.username}</Text>
</View>
{this.renderRightButton()}
{renderRightButton && this.renderRightButton()}
</TouchableOpacity>
);
}
......
......@@ -59,7 +59,7 @@ import featuresService from '../common/services/features.service';
*/
const Stack = createStackNavigator({
Tabs: {
screen: featuresService.has('new-navigation-january-2020')
screen: featuresService.has('navigation-2020')
? withErrorBoundaryScreen(TabsScreenNew)
: withErrorBoundaryScreen(TabsScreen)
},
......
......@@ -32,9 +32,8 @@ import i18n from '../common/services/i18n.service';
export default class NewsfeedScreen extends Component {
static navigationOptions = {
header: props => <TopbarNew {...props} />,
tabBarIcon: ({ tintColor }) => (
featuresService.has('new-navigation-january-2020')
featuresService.has('navigation-2020')
? <TabIcon name="md-home" color={tintColor} />
: <IonIcon name="md-home" size={24} color={tintColor} />
),
......
......@@ -158,9 +158,9 @@ class NewsfeedStore {
prepend(entity) {
const model = ActivityModel.checkOrCreate(entity);
model.listRef = this.listRef.listRef;
this.feedStore.prepend(model);
model.listRef = this.listRef.listRef;
}
@action
......
......@@ -133,20 +133,65 @@ export default class Activity extends Component {
{ overlay }
</View>
{ this.showActions() }
{ this.props.entity.isScheduled() &&
<View style={[{backgroundColor: '#ffecb3'}, CommonStyle.padding]}>
<Text style={[styles.scheduledText, CommonStyle.paddingLeft]}>
{`${i18n.t('activity.scheduled')} ${formatDate(this.props.entity.time_created)}.`}
</Text>
</View> }
{ this.props.isLast ? <View style={styles.activitySpacer}></View> : null}
{ !this.props.hideTabs &&
!this.props.entity.isScheduled() &&
<ActivityMetrics entity={this.props.entity}/> }
{ this.renderScheduledMessage() }
{ this.renderPendingMessage() }
{ this.renderActivitySpacer() }
{ this.renderActivityMetrics() }
</View>
);
}
/**
* Render activity spacer
*/
renderActivitySpacer = () => {
return this.props.isLast
? (<View style={styles.activitySpacer}></View>)
: null;
};
/**
* Render entity metrics
*/
renderActivityMetrics = () => {
return (
!this.props.hideTabs &&
!this.props.entity.isScheduled() &&
!this.props.entity.isPending()
) ? (<ActivityMetrics entity={this.props.entity}/>) : null
};
/**
* Show message if entity is scheduled
*/
renderScheduledMessage = () => {
return this.props.entity.isScheduled()
? (this.renderYellowBanner(`${i18n.t('activity.scheduled')} ${formatDate(this.props.entity.time_created)}.`))
: null;
};
/**
* Show message if entity is awaiting moderation
*/
renderPendingMessage = () => {
return this.props.entity.isPending()
? (this.renderYellowBanner(i18n.t('activity.pendingModeration')))
: null;
};
/**
* Render a banner with a message bellow the activity
*/
renderYellowBanner = message => {
return (
<View style={[styles.yellowBanner, CommonStyle.padding]}>
<Text style={[styles.yellowBannerText, CommonStyle.paddingLeft]}>
{message}
</Text>
</View>
);
};
/**
* Pause video if exist
*/
......@@ -348,8 +393,11 @@ const styles = StyleSheet.create({
blockedNoticeDesc: {
opacity: 0.7,
},
scheduledText: {
yellowBannerText: {
fontSize: 11,
color: '#000',
},
yellowBanner: {
backgroundColor: '#ffecb3',
}
});
......@@ -34,9 +34,10 @@ import HashtagsStepNew from './steps/HashtagsStepNew';
import ChannelSetupStepNew from './steps/ChannelSetupStepNew';
import SuggestedGroupsStepNew from './steps/SuggestedGroupsStepNew';
@observer
export default
@inject('onboarding', 'hashtag', 'groupsBar', 'discovery')
export default class OnboardingScreenNew extends Component {
@observer
class OnboardingScreenNew extends Component {
/**
* Disable navigation bar
......@@ -124,16 +125,17 @@ export default class OnboardingScreenNew extends Component {
steps.push({component: <ChannelSetupStepNew ref={r => this.channelSetup = r} onNext={this.onNext} onBack={this.onBack}/> });
}
if (!completed_items.some(r => r == 'suggested_groups')) {
steps.push({component: <SuggestedGroupsStepNew onNext={this.onNext} onBack={this.onBack}/>});
}
// TODO: enable group and channel selectors
// if (!completed_items.some(r => r == 'suggested_groups')) {
// steps.push({component: <SuggestedGroupsStepNew onNext={this.onNext} onBack={this.onBack}/>});
// }
if (!completed_items.some(r => r == 'suggested_channels')) {
steps.push({component: <SuggestedChannelsStepNew onNext={this.onNext} onBack={this.onBack}/>});
}
// if (!completed_items.some(r => r == 'suggested_channels')) {
// steps.push({component: <SuggestedChannelsStepNew onNext={this.onNext} onBack={this.onBack}/>});
// }
return (
<SafeAreaView style={[CS.flexContainer, CS.backgroundThemePrimary]}>
<SafeAreaView style={[CS.flexContainer, CS.backgroundPrimary]}>
<KeyboardAvoidingView style={[CS.flexContainer]} behavior={ Platform.OS == 'ios' ? 'padding' : null }>
<Wizard steps={steps} onFinish={this.onFinish} ref={this.handleWizarRef}></Wizard>
</KeyboardAvoidingView>
......
......@@ -39,13 +39,13 @@ class OnboardingStore {
/**
* Get progress
*/
async getProgress() {
async getProgress(navigate = true) {
logService.info('[OnboardingStore] getting onboarding progress');
let onboarding = featuresService.has('onboarding-december-2019') ? 'OnboardingScreenNew' : 'OnboardingScreen';
try {
const progress = await onboardingService.getProgress();
this.setProgress(progress);
if (progress && progress.show_onboarding) {
if (progress && progress.show_onboarding && navigate) {
NavigationService.push(onboarding);
}
return progress;
......
......@@ -21,7 +21,7 @@ export default class AllDoneStep extends Component {
<View
style={[
CS.flexContainerCenter,
CS.backgroundThemePrimary,
CS.backgroundPrimary,
CS.centered,
]}>
<Text style={[CS.onboardingTitle]}>{i18n.t('boosts.tabNewsfeed')}</Text>
......
......@@ -45,7 +45,7 @@ export default class ChannelSetupStepNew extends Component {
constructor(props) {
super(props);
this.store = this.props.channel.store(sessionService.guid);
this.store = this.props.channel.store(sessionService.guid);
}
changeAvatarAction = async () => {
......@@ -84,7 +84,7 @@ export default class ChannelSetupStepNew extends Component {
if (this.store.isUploading) throw new UserError('Avatar is uploading, please wait');
const {phoneNumber, city, dob} = this.state;
const payload = {
phoneNumber,
city,
......@@ -192,10 +192,10 @@ export default class ChannelSetupStepNew extends Component {
render() {
return (
<View style={[CS.flexContainerCenter]}>
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutBody, CS.backgroundPrimary]}>
{this.getBody()}
</View>
{ this.state.showFooter && <View style={[CS.mindsLayoutFooter, CS.backgroundThemePrimary]}>
{ this.state.showFooter && <View style={[CS.mindsLayoutFooter, CS.backgroundPrimary]}>
{this.getFooter()}
</View>}
</View>
......
......@@ -60,10 +60,10 @@ export default class HashtagsStepNew extends Component {
render() {
return (
<View style={[CS.flexContainerCenter]}>
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutBody, CS.backgroundPrimary]}>
{this.getBody()}
</View>
<View style={[CS.mindsLayoutFooter, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutFooter, CS.backgroundPrimary]}>
{this.getFooter()}
</View>
</View>
......
......@@ -34,7 +34,7 @@ export default class SuggestedChannelsStepNew extends Component {
* Component did mount
*/
componentDidMount() {
}
/**
......@@ -83,10 +83,10 @@ export default class SuggestedChannelsStepNew extends Component {
render() {
return (
<View style={[CS.flexContainerCenter]}>
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutBody, CS.backgroundPrimary]}>
{this.getBody()}
</View>
<View style={[CS.mindsLayoutFooter, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutFooter, CS.backgroundPrimary]}>
{this.getFooter()}
</View>
</View>
......
......@@ -25,7 +25,7 @@ export default class SuggestedGroupsStepNew extends Component {
this.props.discovery.init();
}
componentDidMount() {
this.props.hashtag.setAll(false);
this.props.discovery.filters.setType('groups');
......@@ -68,10 +68,10 @@ export default class SuggestedGroupsStepNew extends Component {
render() {
return (
<View style={[CS.flexContainerCenter]}>
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutBody, CS.backgroundPrimary]}>
{this.getBody()}
</View>
<View style={[CS.mindsLayoutFooter, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutFooter, CS.backgroundPrimary]}>
{this.getFooter()}
</View>
</View>
......
......@@ -67,10 +67,10 @@ export default class WelcomeStep extends Component {
render() {
return (
<View style={[CS.flexContainerCenter]} testID="artTestID">
<View style={[CS.mindsLayoutBody, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutBody, CS.backgroundPrimary]}>
{this.getBody()}
</View>
<View style={[CS.mindsLayoutFooter, CS.backgroundThemePrimary]}>
<View style={[CS.mindsLayoutFooter, CS.backgroundPrimary]}>
{this.getFooter()}
</View>
</View>
......
......@@ -31,11 +31,14 @@ import logService from '../common/services/log.service';
import storageService from '../common/services/storage.service';
import { observer } from 'mobx-react/native';
import ModalPicker from '../common/components/ModalPicker';
import ThemedStyles from '../styles/ThemedStyles';
import featuresService from '../common/services/features.service';
const ICON_SIZE = 24;
export default
@observer
export default class SettingsScreen extends Component {
class SettingsScreen extends Component {
static navigationOptions = {
title: 'Settings',
......@@ -61,6 +64,14 @@ export default class SettingsScreen extends Component {
settingsStore.setLeftHanded(!settingsStore.leftHanded);
}
setDarkMode = () => {
if (ThemedStyles.theme) {
ThemedStyles.setLight();
} else {
ThemedStyles.setDark();
}
}
wipeEthereumKeychainAction = () => {
const _confirm3 = async (confirmation) => {
await new Promise(r => setTimeout(r, 500)); // Modals have a "cooldown"
......@@ -96,54 +107,55 @@ export default class SettingsScreen extends Component {
};
render() {
const CS = ThemedStyles.style;
const languages = i18n.getSupportedLocales();
const list = [
{
name: i18n.t('language')+` (${i18n.getCurrentLocale()})`,
icon: (<Icon name='flag' size={ICON_SIZE} style={ styles.icon }/>),
icon: (<Icon name='flag' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
onPress: () => {
this.showLanguages();
}
},
{
name: i18n.t('auth.password'),
icon: (<Icon name='security' size={ICON_SIZE} style={ styles.icon }/>),
icon: (<Icon name='security' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
onPress: () => {
this.props.navigation.navigate('SettingsPassword');
}
},
{
name: i18n.t('auth.email'),
icon: (<Icon name='email' size={ICON_SIZE} style={ styles.icon }/>),
icon: (<Icon name='email' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
onPress: () => {
this.props.navigation.navigate('SettingsEmail');
}
},
{
name: i18n.t('settings.pushNotification'),
icon: (<Icon name='notifications' size={ICON_SIZE} style={ styles.icon }/>),
icon: (<Icon name='notifications' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
onPress: () => {
this.props.navigation.navigate('NotificationsSettings');
}
},
{
name: i18n.t('settings.blockedChannels'),
icon: (<Icon name='block' size={ICON_SIZE} style={ styles.icon }/>),
icon: (<Icon name='block' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
onPress: () => {
this.props.navigation.navigate('SettingsBlockedChannels');
}
},
{
name: i18n.t('settings.regenerateKey'),
icon: (<Icon name='vpn-key' size={ICON_SIZE} style={ styles.icon }/>),
icon: (<Icon name='vpn-key' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
onPress: () => {
this.props.navigation.navigate('SettingsRekey');
}
},
{
name: i18n.t('settings.logout'),
icon: (<Icon name='power-settings-new' size={ICON_SIZE} style={ styles.icon } />),
icon: (<Icon name='power-settings-new' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]} />),
onPress: () => {
authService.logout();
......@@ -152,78 +164,76 @@ export default class SettingsScreen extends Component {
},
{
name: i18n.t('settings.deactivate'),
icon: (<Icon name='warning' size={ICON_SIZE} style={ styles.icon } />),
icon: (<Icon name='warning' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]} />),
onPress: () => {
this.props.navigation.push('DeleteChannel');
}
},
{
name: i18n.t('settings.deleteBlockchain'),
icon: (<Icon name='warning' size={ICON_SIZE} style={ styles.icon } />),
icon: (<Icon name='warning' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]} />),
onPress: this.wipeEthereumKeychainAction
},
// ListView used by log package is deprecated
// {
// name: i18n.t('settings.logs'),
// icon: (<Icon name='list' size={ICON_SIZE} style={ styles.icon }/>),
// icon: (<Icon name='list' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
// onPress: () => {
// this.props.navigation.push('Logs');
// }
// },
{
name: i18n.t('settings.logOnlyErrors'),
icon: (<Icon name='list' size={ICON_SIZE} style={ styles.icon }/>),
switchButton: true,
icon: (<Icon name='list' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
switch: {value: !settingsStore.appLog, onValueChange: this.appLogActivate},
hideChevron: true,
switched: !settingsStore.appLog,
onSwitch: this.appLogActivate
},
{
name: i18n.t('settings.leftHandedMode'),
icon: (<MaterialCommunityIcons name='hand' size={ICON_SIZE} style={ styles.icon }/>),
switchButton: true,
icon: (<MaterialCommunityIcons name='hand' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
switch: {value: settingsStore.leftHanded, onValueChange: this.leftHandedActivate},
hideChevron: true,
switched: settingsStore.leftHanded,
onSwitch: this.leftHandedActivate
},
];
if (featuresService.has('dark-mode')) {
list.push({
name: i18n.t('settings.darkMode'),
icon: (<MaterialCommunityIcons name='hand' size={ICON_SIZE} style={[styles.icon, CS.colorPrimaryText]}/>),
switch: {value: !!ThemedStyles.theme, onValueChange: this.setDarkMode},
hideChevron: true,
});
}
return (
<ScrollView style={styles.scrollView}>
<ModalPicker
onSelect={this.languageSelected}
onCancel={this.cancel}
show={this.state.showLanguages}
title={i18n.t('language')}
valueField="value"
labelField="name"
value={this.state.language}
items={languages}
/>
<View style={styles.scrollViewContainer}>
<View style={styles.container}>
{
list.map((l, i) => (
<ListItem
key={i}
title={l.name}
titleStyle={styles.listTitle}
containerStyle={styles.listItem}
subtitle={l.subtitle}
switchButton={l.switchButton}
hideChevron ={l.hideChevron}
onSwitch={l.onSwitch}
switched={l.switched}
leftIcon={l.icon}
onPress= {l.onPress}
noBorder
/>
))
}
</View>
</View>
</ScrollView>
<ScrollView style={[styles.scrollView, CS.backgroundPrimary]}>
<ModalPicker
onSelect={this.languageSelected}
onCancel={this.cancel}
show={this.state.showLanguages}
title={i18n.t('language')}
valueField="value"
labelField="name"
value={this.state.language}
items={languages}
/>
{
list.map((l, i) => (
<ListItem
key={i}
title={l.name}
titleStyle={[CS.fontL, CS.colorPrimaryText, CS.paddingVertical2x]}
containerStyle={styles.listItem}
subtitle={l.subtitle}
hideChevron ={l.hideChevron}
switch={l.switch}
leftIcon={l.icon}
onPress= {l.onPress}
/>
))
}
</ScrollView>
);
}
......@@ -246,10 +256,8 @@ export default class SettingsScreen extends Component {
const styles = StyleSheet.create({
scrollView: {
backgroundColor: '#FFF',
flexDirection: 'column',
},
scrollViewContainer: {
flex:1
},
container: {
flex: 1,
......@@ -258,10 +266,7 @@ const styles = StyleSheet.create({
borderBottomWidth: 0,
},
listItem: {
borderBottomWidth: 1,
borderBottomColor: '#ddd',
paddingTop: 8,
paddingBottom: 8,
backgroundColor: 'transparent'
//height:20
},
listTitle: {
......@@ -269,7 +274,6 @@ const styles = StyleSheet.create({
fontFamily: 'Roboto',
},
icon: {
color: '#455a64',
alignSelf: 'center',
},
......@@ -278,7 +282,7 @@ const styles = StyleSheet.create({
paddingTop: 8,
paddingBottom: 8,
textAlignVertical: 'center',
backgroundColor: '#f4f4f4',
// backgroundColor: '#f4f4f4',
width: '100%',
//height: 40,
borderTopWidth: StyleSheet.hairlineWidth,
......
import { observable, action } from 'mobx'
import { observable, action } from 'mobx';
import logService from '../common/services/log.service';
import storageService from '../common/services/storage.service';
import appStore from '../../AppStores';
import ThemedStyles from '../styles/ThemedStyles';
/**
* Store for the values held in Settings.
......@@ -22,8 +22,25 @@ class SettingsStore {
*/
@action.bound
async init() {
const data = await storageService.multiGet(['LeftHanded', 'AppLog', 'CreatorNsfw', 'ConsumerNsfw', 'UseHashtags']);
if (!data) return;
const data = await storageService.multiGet([
'LeftHanded',
'AppLog',
'CreatorNsfw',
'ConsumerNsfw',
'UseHashtags',
'Theme',
]);
// store theme changes
ThemedStyles.onThemeChange((value) => {
this.setTheme(value);
});
if (!data) {
ThemedStyles.theme = 0;
ThemedStyles.init();
return;
}
this.leftHanded = data[0][1];
this.appLog = data[1][1];
this.creatorNsfw = data[2][1] || [];
......@@ -33,9 +50,21 @@ class SettingsStore {
// set the initial value for hashtag
appStore.hashtag.setAll(!this.useHashtags);
// theme
ThemedStyles.theme = data[5][1] || 0;
ThemedStyles.init();
return this;
}
/**
* Set the theme in the stored values
* @param {numeric} value
*/
setTheme(value) {
storageService.setItem('Theme', value);
}
/**
* Sets in local store and changes this class variable
*/
......
......@@ -22,6 +22,12 @@ class ShareService {
* @param {string} url
*/
share(title, url) {
// added this because if tittle (activity text) is too long, causes problem in android
title = title.length > 50
? title.substring(0, 46) + '...'
: title;
// added a settimeout as a workaround for ios, without it the share dialog is not shown
setTimeout(async () => {
try {
......
......@@ -24,6 +24,7 @@ export const LIGHT_THEME = {
done: '#4C92A4',
action: '#A5A5A5',
icon: '#777777',
link: '#0091FF',
};
export const DARK_THEME = {
......@@ -37,4 +38,5 @@ export const DARK_THEME = {
done: '#4C92A4',
action: '#A5A5A5',
icon: '#BEC0C1',
link: '#0091FF',
};
export const CommonStyled = {
textTitle: `
font-family: Roboto;
font-size: 28px;
font-weight: bold;
line-height: 44px;
`,
textSubTitle: `
font-family: Roboto;
font-size: 17px;
font-weight: 500;
line-height: 23px;
`,
flexColumnCentered: `
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
align-content: center;
`,
};
......@@ -101,9 +101,10 @@ export const ComponentsStyle = StyleSheet.create({
},
registerCheckboxNew: {
backgroundColor: 'transparent',
marginLeft: 0,
paddingLeft: 0,
borderWidth: 0,
alignSelf: 'flex-start',
marginTop: 15
marginTop: 15,
},
//button
......
import { StyleSheet } from 'react-native';
import { observable, action, reaction } from 'mobx';
import { DARK_THEME, LIGHT_THEME } from './Colors';
const repetitions = 8;
const step = 5;
const dynamicStyles = {};
for (let index = 0; index < repetitions; index++) {
let value = step * index;
const post = index === 1 ? '' : `${index}x`;
dynamicStyles[`margin${post}`] = { margin: value };
dynamicStyles[`marginVertical${post}`] = { marginVertical: value };
dynamicStyles[`marginTop${post}`] = { marginTop: value };
dynamicStyles[`marginLeft${post}`] = { marginLeft: value };
dynamicStyles[`marginRight${post}`] = { marginRight: value };
dynamicStyles[`marginBottom${post}`] = { marginBottom: value };
dynamicStyles[`marginHorizontal${post}`] = { marginHorizontal: value };
dynamicStyles[`padding${post}`] = { padding: value };
dynamicStyles[`paddingVertical${post}`] = { paddingVertical: value };
dynamicStyles[`paddingTop${post}`] = { paddingTop: value };
dynamicStyles[`paddingLeft${post}`] = { paddingLeft: value };
dynamicStyles[`paddingRight${post}`] = { paddingRight: value };
dynamicStyles[`paddingBottom${post}`] = { paddingBottom: value };
dynamicStyles[`paddingHorizontal${post}`] = { paddingHorizontal: value };
}
/**
* ThemedStylesStore
*/
class ThemedStylesStore {
/**
* Theme observable
* 1 Dark
* 0 Light
* -1 Not loaded
* @property {Observable<numeric>}
*/
@observable theme = -1;
/**
* Style
*/
style = {};
/**
* Initialice themed styles
*/
async init() {
// load stored theme value here
this.generateStyle();
}
@action
setDark() {
this.theme = 1;
this.generateStyle();
}
@action
setLight() {
this.theme = 0;
this.generateStyle();
}
onThemeChange(fn) {
return reaction(() => [this.theme], async args => await fn(...args), {
fireImmediately: false,
});
}
/**
* Get color of theme based on property
* @param {String} prop
*/
getColor(prop) {
const theme = this.theme ? DARK_THEME : LIGHT_THEME;
return theme[prop];
}
/**
* Generates the current theme
*/
generateStyle() {
const theme = this.theme ? DARK_THEME : LIGHT_THEME;
this.style = StyleSheet.create({
...dynamicStyles,
// containers
flexContainer: {
flex: 1,
},
flexContainerCenter: {
flex: 1,
justifyContent: 'center',
},
flexColumn: {
flex: 1,
flexDirection: 'column',
},
flexColumnStretch: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'stretch'
},
flexColumnCentered: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
alignContent: 'center',
},
rowJustifyEnd: {
flexDirection: 'row',
justifyContent: 'flex-end'
},
rowJustifyCenter: {
flexDirection: 'row',
justifyContent: 'center'
},
rowJustifySpaceEvenly: {
flexDirection: 'row',
justifyContent: 'space-evenly'
},
rowJustifyStart: {
flexDirection: 'row',
justifyContent: 'flex-start'
},
centered: {
alignContent: 'center',
alignItems: 'center',
alignSelf: 'center',
justifyContent: 'center',
},
colorWhite: {
color: '#FFFFFF'
},
colorBlack: {
color: '#000000'
},
colorPrimaryText: {
color: theme.primary_text
},
colorSecondaryText: {
color: theme.secondary_text
},
colorLink: {
color: theme.link
},
colorButton: {
color: theme.button_border
},
colorDone: {
color: theme.done
},
colorActionNew: {
color: theme.action
},
colorIcon: {
color: theme.icon
},
// backgrounds
backgroundWhite: {
backgroundColor: 'white'
},
backgroundBlack: {
backgroundColor: 'black'
},
backgroundPrimary: {
backgroundColor: theme.primary_background,
},
backgroundSecondary: {
backgroundColor: theme.secondary_background,
},
// fonts
fontXS: {
fontSize: 10
},
fontS: {
fontSize: 12
},
fontM: {
fontSize: 14
},
fontL: {
fontSize: 16
},
fontXL: {
fontSize: 18
},
fontXXL: {
fontSize: 24
},
fontXXXL: {
fontSize: 30
},
// text align
textRight: {
textAlign: 'right'
},
textLeft: {
textAlign: 'left'
},
textCenter: {
textAlign: 'center'
},
textJustify: {
textAlign: 'justify'
},
fullWidth: {
width: '100%'
},
halfWidth: {
width: '50%'
},
bold: {
fontWeight: '700'
},
extraBold: {
// fontWeight: '800'
fontFamily: 'Roboto-Black', // workaround android ignoring >= 800
},
fontThin: {
fontWeight: '200'
},
fontHairline: {
fontWeight: '100'
},
fontLight: {
fontWeight: '300'
},
fontNormal: {
fontWeight: '400'
},
fontMedium: {
fontWeight: '500'
},
fontSemibold: {
fontWeight: '600'
},
// onboarding
onboardingTitle: {
color: '#AEB0B8',
fontSize: 13,
lineHeight: 18,
letterSpacing: 2,
},
onboardingSubtitle: {
color: '#4A4A4A',
fontSize: 26,
lineHeight: 37,
fontWeight: '600',
},
onboardingSteps: {
color: '#A2A2A2',
fontSize: 11,
lineHeight: 15,
},
linkNew: {
color: '#9B9B9B',
fontSize: 13,
lineHeight: 20
},
mindsLayoutBody: {
flex: 10,
flexDirection: 'row',
justifyContent: 'center',
paddingHorizontal: 20,
paddingTop: 20,
paddingBottom: 10,
},
mindsLayoutFooter: {
flex: 2,
flexDirection: 'row',
justifyContent: 'center',
paddingHorizontal: 20,
paddingTop: 20,
paddingBottom: 20,
},
titleText: {
fontFamily: 'Roboto',
fontSize: 28,
fontWeight: 'bold',
lineHeight: 44,
},
subTitleText: {
fontFamily: 'Roboto',
fontSize: 17,
fontWeight: '500',
lineHeight: 23,
},
// inputs
input: {
color: theme.primary_text,
fontSize: 16,
padding: 10,
fontFamily: 'Roboto',
backgroundColor: 'transparent',
height: 50,
borderRadius: 2,
borderColor: theme.button_border,
borderWidth: 1,
lineHeight: 21,
},
link: {
color: theme.link,
textDecorationLine: 'underline',
},
inputIcon: {
position: 'absolute',
right:8,
top: Platform.OS === 'ios' ? 36 : 40,
color: theme.primary_text,
},
button: {
marginRight: 0,
marginLeft: 0,
backgroundColor: '#5DBAC0',
borderColor: '#5DBAC0',
borderWidth: 1,
borderRadius: 2,
height: 60,
},
buttonText: {
// fontFamily: 'Roboto',
fontSize: 20,
fontWeight: '500',
color: 'white',
},
checkbox: {
backgroundColor: 'transparent',
marginLeft: 0,
paddingLeft: 0,
borderWidth: 0,
marginTop: 15,
},
});
}
}
export default new ThemedStylesStore();
......@@ -12,6 +12,7 @@ import {
TouchableOpacity,
SafeAreaView,
Image,
TouchableHighlight,
} from 'react-native';
import {
......@@ -31,6 +32,8 @@ import TabIcon from './TabIcon';
import abbrev from '../common/helpers/abbrev';
import shareService from '../share/ShareService';
import featuresService from '../common/services/features.service';
import authService from './../auth/AuthService';
import ThemedStyles from '../styles/ThemedStyles';
const ICON_SIZE = 24;
......@@ -61,6 +64,8 @@ class MoreScreenNew extends Component {
* Return Options List ready to be rendered
*/
getOptionsList = () => {
const CS = ThemedStyles.style;
let list = [
{
name: i18n.t('moreScreen.helpSupport'),
......@@ -133,11 +138,13 @@ class MoreScreenNew extends Component {
* @param {Array} list
*/
renderList = list => {
const CS = ThemedStyles.style;
return (
<ScrollView
style={[
styles.container,
CS.backgroundThemePrimary,
CS.backgroundPrimary,
CS.marginTop4x,
]}
>
......@@ -147,7 +154,7 @@ class MoreScreenNew extends Component {
key={i}
title={l.name}
titleStyle={[CS.titleText, CS.colorPrimaryText, CS.padding, CS.fontXXL]}
containerStyle={[styles.listItem, CS.backgroundThemePrimary]}
containerStyle={[styles.listItem, CS.backgroundPrimary]}
switchButton={l.switchButton}
hideChevron ={l.hideChevron}
leftIcon={l.icon}
......@@ -165,15 +172,19 @@ class MoreScreenNew extends Component {
const avatar = this.getAvatar(),
channel = this.props.user.me;
const CS = ThemedStyles.style;
return (
<SafeAreaView style={[
CS.flexContainer,
CS.backgroundThemePrimary,
CS.backgroundPrimary,
]}>
{/* CHANNEL DATA */}
<View style={styles.headerContainer}>
<Image source={avatar} style={[styles.wrappedAvatar]} />
<View style={styles.headerContainer} >
<TouchableOpacity onPress={this.navToChannel}>
<Image source={avatar} style={styles.wrappedAvatar}/>
</TouchableOpacity>
<Text style={[CS.titleText, CS.colorPrimaryText, CS.marginTop2x]}>{channel.name}</Text>
<Text style={[CS.subTitleText, CS.colorSecondaryText, CS.fontNormal]}>@{channel.username}</Text>
<Text style={[CS.subTitleText, CS.colorSecondaryText, CS.fontNormal, CS.marginTop3x]}>
......
......@@ -90,22 +90,12 @@ const Tabs = (
}
},
initialRouteName: 'Newsfeed',
navigationOptions: {
header: props => <Topbar {...props} />,
}
})
);
const inset = { bottom: 'always', top: 'never' };
export default class TabsScreen extends Component {
// link router between tab and main stack navigator
static router = Tabs.router;
static navigationOptions = {
header: props => <Topbar {...props} />,
}
render() {
return (
<Tabs navigation={this.props.navigation}/>
);
}
}
export default Tabs
......@@ -22,11 +22,12 @@ import DiscoveryScreen from '../discovery/DiscoveryScreen';
import featuresService from '../common/services/features.service';
import { withErrorBoundaryScreen } from '../common/components/ErrorBoundary';
import isIphoneX from '../common/helpers/isIphoneX';
import { CommonStyle as CS } from '../styles/Common';
import CapturePoster from '../capture/CapturePoster';
import TopbarNew from '../topbar/TopbarNew';
import MoreScreenNew from './MoreScreenNew';
import ThemedStyles from '../styles/ThemedStyles';
let screens = {
Newsfeed: {
......@@ -79,9 +80,8 @@ const Tabs = (
activeTintColor: '#0091FF',
inactiveTintColor: '#777777',
style: {
...CS.backgroundThemeSecondary,
marginBottom: isIphoneX ? 10 : null,
height: 60,
backgroundColor: ThemedStyles.getColor('secondary_background'),
height: 70,
},
indicatorStyle: {
marginBottom: isIphoneX ? 10 : null,
......@@ -90,26 +90,16 @@ const Tabs = (
iconStyle: {
height: 44,
width: 44,
...CS.centered,
...ThemedStyles.style.centered,
}
},
initialRouteName: 'Newsfeed',
navigationOptions: {
header: null,
}
})
);
const inset = { bottom: 'always', top: 'never' };
export default class TabsScreenNew extends Component {
// link router between tab and main stack navigator
static router = Tabs.router;
static navigationOptions = {
header: null,
}
render() {
return (
<Tabs navigation={this.props.navigation}/>
);
}
}
export default Tabs;
import sessionService from "../common/services/session.service";
import storageService from "../common/services/storage.service";
import apiService from "../common/services/api.service";
import UserModel from "../channel/UserModel";
class SearchBarService {
/**
* The user search history
*/
searchHistory;
/**
* The key to look in storage
* composed by user guid
*/
storageKey;
init(guid) {
this.storageKey = `${guid}:searchHistory`;
this.searchHistory = storageService.getItem(this.storageKey);
}
/**
* Call suggested endpoint
* @param {String} search
*/
async getSuggestedSearch(search) {
const res = await apiService.get('api/v2/search/suggest', {q: search, limit: 4})
return UserModel.createMany(res.entities);
}
/**
* Retrieve search history
*/
async getSearchHistory() {
this.searchHistory = await this.searchHistory || []
return this.searchHistory;
}
/**
* Every time an item is tapped on search bar, we stored in search history
* @param {String} item
*/
async onItemTap(item) {
// If item already exists in history, remove it
const index = this.searchHistory.indexOf(item);
if (index !== -1) {
this.searchHistory.splice(index, 1);
}
// add item at the begining of history
if (this.searchHistory.unshift(item) > 5) {
this.searchHistory.pop();
}
await storageService.setItem(this.storageKey, this.searchHistory);
}
async clearSearchHistory() {
this.searchHistory = [];
await storageService.setItem(this.storageKey, this.searchHistory);
}
}
export default new SearchBarService();
\ No newline at end of file
......@@ -2,10 +2,13 @@ import React, { Component } from 'react';
import { observer } from 'mobx-react/native'
import Icon from 'react-native-vector-icons/MaterialIcons';
import { StyleSheet, View } from 'react-native';
import { CommonStyle as CS } from '../styles/Common';
import { StyleSheet, View, SafeAreaView } from 'react-native';
import i18n from '../common/services/i18n.service';
import TextInput from '../common/components/TextInput';
import SearchResult from './SearchResultComponent';
import Modal from 'react-native-modal';
import ThemedStyles from '../styles/ThemedStyles';
export default
@observer
......@@ -35,40 +38,62 @@ class SearchComponent extends Component {
isSearching = () => this.props.user.searching;
/**
*
* set search text
*/
search = searchText => this.setState( {searchText} );
search = searchText => {
this.setState( {searchText} );
this.searchResult.input(searchText);
}
handleSearchResultRef = ref => this.searchResult = ref;
render() {
const CS = ThemedStyles.style;
return (
<View style={[this.isSearching() ? styles.width100 : {}]}>
<View>
<Icon
onPress={!this.isSearching() ? this.toggleSearching : null}
name="search"
size={24}
style={[ styles.button, CS.colorIcon ]}
/>
{
this.isSearching() &&
<TextInput
placeholder={i18n.t('discovery.search')}
onChangeText={this.search}
value={this.state.searchText}
testID="searchInput"
style={[CS.paddingLeft2x]}
/>
}
{
this.isSearching() &&
(<View style={styles.close}>
<Icon
onPress={this.toggleSearching}
name="close"
size={24}
style={[ styles.button, CS.colorIcon]}
/>
</View>)
}
<Modal
isVisible={this.isSearching()}
backdropColor={ThemedStyles.getColor('secondary_background')}
backdropOpacity={ 1 }
>
<SafeAreaView style={[CS.flexContainer, CS.backgroundSecondary]}>
<View style={[styles.header, CS.marginTop4x, CS.marginBottom4x]}>
<View style={[CS.rowJustifyStart, CS.paddingLeft2x]}>
<Icon
name="search"
size={24}
style={[CS.colorIcon, CS.marginRight2x]}
/>
<TextInput
placeholder={i18n.t('discovery.search')}
onChangeText={this.search}
value={this.state.searchText}
testID="searchInput"
style={styles.textInput}
/>
</View>
<Icon
onPress={this.toggleSearching}
name="close"
size={18}
style={[styles.button, CS.colorIcon]}
/>
</View>
<SearchResult
user={this.props.user}
ref={this.handleSearchResultRef}
navigation={this.props.navigation}
search={this.search}/>
</SafeAreaView>
</Modal>
</View>
);
}
......@@ -78,15 +103,11 @@ const styles = StyleSheet.create({
button: {
paddingHorizontal: 8,
},
width100: {
width: '150%',
position: 'absolute',
right: 0,
header: {
flexDirection: 'row',
justifyContent: 'space-between',
},
close: {
flex: 1,
alignItems: 'flex-end',
justifyContent: 'flex-end',
textInput: {
width: '60%'
}
});
import React, { Component } from 'react';
import { StyleSheet, View, ScrollView, Text, TouchableHighlight } from 'react-native';
import CenteredLoading from '../common/components/CenteredLoading';
import DiscoveryUserNew from '../discovery/DiscoveryUserNew';
import i18n from '../common/services/i18n.service';
import FAIcon from 'react-native-vector-icons/FontAwesome5';
import debounce from '../common/helpers/debounce';
import ThemedStyles from '../styles/ThemedStyles';
export default class SearchResultComponent extends Component {
search = '';
state = {
loading: false,
suggested: [],
history: [],
}
async componentDidMount() {
const history = await this.props.user.getSearchHistory();
this.setState({ history });
}
/**
* Only show suggested when search length > 2
*/
shouldShowSuggested = () => this.search.length > 2;
input = async search => {
this.search = search;
if (this.shouldShowSuggested()) {
this.setState({ loading: true })
const suggested = await this.props.user.getSuggestedSearch(search);
this.setState({
suggested: suggested,
loading: false
});
}
}
/**
* List based on what user has typed
*/
renderSuggestedSearch = () => {
const suggestedSearch = this.state.suggested.length === 0
? (
this.state.loading
? <CenteredLoading />
: this.renderEmptyMessageSuggest()
)
: (
<ScrollView keyboardShouldPersistTaps='handled'>
{this.state.suggested.map(this.renderUser)}
</ScrollView>
)
return suggestedSearch;
}
/**
* Render user
*/
renderUser = (user, index) => {
return <DiscoveryUserNew
row={{item: user}}
key={user.guid}
testID={`suggestedUser${index}`}
onUserTap={this.searchBarItemTap}
subscribe={false}
navigation={this.props.navigation}
/>
}
/**
* List based on user search history
* rendered when nothing has been typed
*/
renderSearchHistory = () => {
const CS = ThemedStyles.style;
const searchHistory = this.state.history.length === 0
? (this.renderEmptyMessageHistory())
: (
<ScrollView keyboardShouldPersistTaps='handled'>
{this.state.history.map(item => (
<Text
onPress={() => this.props.search(item)}
style={[
CS.subTitleText,
CS.colorSecondaryText,
CS.fontLight,
CS.marginTop3x,
CS.marginLeft2x
]}
>
{item}
</Text>))}
</ScrollView>
)
return [(
<View style={[styles.row, CS.marginBottom3x]}>
<Text style={[
CS.subTitleText,
CS.colorSecondaryText,
CS.fontM,
]}>{i18n.t('searchBar.searchHistory')}</Text>
<Text
style={[
CS.subTitleText,
CS.colorSecondaryText,
CS.fontM,
]}
onPress={this.clearSearchHistory}
>{i18n.t('searchBar.clear')}</Text>
</View>),
searchHistory
];
}
/**
* nav to taped channel and clean the search
*/
searchBarItemTap = item => {
this.props.search('');
this.props.user.toggleSearching();
this.props.user.searchBarItemTap(item);
}
/**
* Rendered when suggest comes empty
*/
renderEmptyMessageSuggest = () => {
const CS = ThemedStyles.style;
return (
<View style={[CS.centered]}>
<FAIcon
name="grin-beam-sweat"
size={36}
style={[CS.colorIcon]}
/>
<Text style={[CS.subTitleText, CS.colorSecondaryText]}>
{i18n.t('searchBar.emptySuggested')}
</Text>
</View>
);
}
/**
* Rendered when no search history
*/
renderEmptyMessageHistory = () => {
const CS = ThemedStyles.style;
return (
<View style={[CS.centered]}>
<FAIcon
name="history"
size={36}
style={[CS.colorIcon]}
/>
<Text style={[CS.subTitleText, CS.colorSecondaryText]}>
{i18n.t('searchBar.emptySearchHistory')}
</Text>
</View>
);
};
clearSearchHistory = async () => {
this.props.user.searchBarClearHistory();
this.setState({ history: [] })
}
render() {
const CS = ThemedStyles.style;
// If have something to search, render suggested, else, search history
const render = this.shouldShowSuggested()
? this.renderSuggestedSearch()
: this.renderSearchHistory();
return (
<View style={[CS.backgroundPrimary]}>
{render}
</View>
);
}
}
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 10,
}
});
......@@ -19,9 +19,10 @@ import featuresService from '../common/services/features.service';
import { SafeAreaView } from 'react-navigation';
import isIphoneX from '../common/helpers/isIphoneX';
import testID from '../common/helpers/testID';
import { CommonStyle as CS } from '../styles/Common';
import SearchComponent from './SearchComponent';
import navigation from '../navigation/NavigationService';
import ThemedStyles from '../styles/ThemedStyles';
const forceInset = isIphoneX ? {top: 32} : null
......@@ -37,16 +38,22 @@ export default class TopbarNew extends Component {
listenForSearch = () => this.props.user.searching ? styles.scale0 : {};
render() {
if (!featuresService.has('navigation-2020')) {
return null;
}
const CS = ThemedStyles.style;
const user = this.props.user;
return (
<SafeAreaView style={styles.container} forceInset={forceInset}>
<SafeAreaView style={[styles.container, CS.backgroundSecondary]} forceInset={forceInset}>
<View style={styles.topbar}>
<View style={[styles.topbarLeft, this.listenForSearch()]}>
<Text style={[CS.titleText, CS.colorPrimaryText, {lineHeight:0}]} >{this.props.title}</Text>
<View style={[styles.topbarLeft, CS.marginLeft2x]}>
<Text style={[CS.titleText, CS.colorPrimaryText, styles.lineHeight0]} >{this.props.title}</Text>
</View>
<View style={styles.topbarRight}>
<Icon name="chat-bubble-outline" size={24} style={[styles.button, CS.colorIcon, this.listenForSearch()]}/>
<SearchComponent user={this.props.user} />
<Icon name="chat-bubble-outline" size={24} style={[styles.button, CS.colorIcon]}/>
<SearchComponent user={this.props.user} navigation={navigation} />
</View>
</View>
</SafeAreaView>
......@@ -62,13 +69,15 @@ if (Platform.OS == 'ios') {
}
const styles = StyleSheet.create({
lineHeight0: {
lineHeight:0,
},
container: {
height: topbarHeight,
display: 'flex',
flexDirection: 'row',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#EEE',
...CS.backgroundThemeSecondary,
},
topbar: {
flex: 1,
......@@ -77,7 +86,6 @@ const styles = StyleSheet.create({
paddingBottom: 5,
},
topbarLeft: {
...CS.marginLeft2x,
alignItems: 'flex-end',
justifyContent: 'flex-end',
flexDirection: 'row'
......
This diff is collapsed.