git ssb

1+

Daan Patchwork / manyverse



Commit 34d18b003d73045d55dd58e8a0c8dcbc0bb47db5

ux: implement RawDatabase screen accessible from drawer

Andre Staltz committed on 7/19/2018, 6:26:01 PM
Parent: 2457384782d72e2f309e722376a55bd6578b86f0

Files changed

src/app/components/messages/ShortRawMessage.tsadded
src/app/components/RawFeed.tsadded
src/app/drivers/ssb.tschanged
src/app/index.tschanged
src/app/screens/drawer/intent.tschanged
src/app/screens/drawer/navigation.tschanged
src/app/screens/drawer/view.tschanged
src/app/screens/raw-db/README.mdadded
src/app/screens/raw-db/index.tsadded
src/ssb/opinions/sbot.tschanged
src/app/components/messages/ShortRawMessage.tsView
@@ -1,0 +1,152 @@
1+/**
2+ * MMMMM is a mobile app for Secure Scuttlebutt networks
3+ *
4+ * Copyright (C) 2017 Andre 'Staltz' Medeiros
5+ *
6+ * This program is free software: you can redistribute it and/or modify
7+ * it under the terms of the GNU General Public License as published by
8+ * the Free Software Foundation, either version 3 of the License, or
9+ * (at your option) any later version.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import {PureComponent} from 'react';
21+import {h} from '@cycle/react';
22+import {
23+ Text,
24+ View,
25+ TouchableNativeFeedback,
26+ Image,
27+ StyleSheet,
28+} from 'react-native';
29+import MessageContainer from './MessageContainer';
30+import HumanTime from 'react-human-time';
31+import {MsgId, Msg, PostContent} from 'ssb-typescript';
32+import {authorName} from '../../../ssb/from-ssb';
33+import {Dimensions} from '../../global-styles/dimens';
34+import {Palette} from '../../global-styles/palette';
35+import {Typography} from '../../global-styles/typography';
36+import {isPrivate} from 'ssb-typescript/utils';
37+
38+export const styles = StyleSheet.create({
39+ row: {
40+ flexDirection: 'row',
41+ flex: 1,
42+ },
43+
44+ avatarContainer: {
45+ height: Dimensions.avatarSizeNormal,
46+ width: Dimensions.avatarSizeNormal,
47+ borderRadius: Dimensions.avatarBorderRadius,
48+ backgroundColor: Palette.indigo1,
49+ marginRight: Dimensions.horizontalSpaceSmall,
50+ },
51+
52+ avatar: {
53+ borderRadius: Dimensions.avatarBorderRadius,
54+ position: 'absolute',
55+ top: 0,
56+ left: 0,
57+ right: 0,
58+ bottom: 0,
59+ },
60+
61+ authorColumn: {
62+ flexDirection: 'column',
63+ flex: 1,
64+ alignItems: 'flex-start',
65+ justifyContent: 'space-around',
66+ },
67+
68+ authorName: {
69+ fontSize: Typography.fontSizeNormal,
70+ fontWeight: 'bold',
71+ fontFamily: Typography.fontFamilyReadableText,
72+ color: Palette.brand.text,
73+ minWidth: 120,
74+ },
75+
76+ msgType: {
77+ fontSize: Typography.fontSizeSmall,
78+ fontFamily: Typography.fontFamilyMonospace,
79+ backgroundColor: Palette.brand.darkVoidBackground,
80+ color: Palette.brand.darkText,
81+ },
82+
83+ timestamp: {
84+ fontSize: Typography.fontSizeSmall,
85+ fontFamily: Typography.fontFamilyReadableText,
86+ color: Palette.brand.textWeak,
87+ },
88+});
89+
90+export type Props = {
91+ msg: Msg;
92+ name: string | null;
93+ imageUrl: string | null;
94+ onPress?: (ev: {msgId: MsgId}) => void;
95+};
96+
97+export default class RawMessage extends PureComponent<Props> {
98+ private _onPress() {
99+ const {onPress, msg} = this.props;
100+ if (onPress) {
101+ onPress({msgId: msg.key});
102+ }
103+ }
104+
105+ public render() {
106+ const {msg, name, imageUrl} = this.props;
107+ const avatarUrl = {uri: imageUrl || undefined};
108+ const touchableProps = {
109+ background: TouchableNativeFeedback.SelectableBackground(),
110+ onPress: () => this._onPress(),
111+ };
112+
113+ const authorNameText = h(
114+ Text,
115+ {
116+ numberOfLines: 1,
117+ ellipsizeMode: 'middle',
118+ style: styles.authorName,
119+ },
120+ authorName(name, msg),
121+ );
122+
123+ const msgTypeText = h(
124+ Text,
125+ {style: styles.msgType},
126+ isPrivate(msg)
127+ ? 'encrypted'
128+ : 'type: ' + (msg.value.content as PostContent).type,
129+ );
130+
131+ const timestampText = h(Text, {style: styles.timestamp}, [
132+ h(HumanTime as any, {time: msg.value.timestamp}),
133+ ]);
134+
135+ return h(TouchableNativeFeedback, touchableProps, [
136+ h(MessageContainer, [
137+ h(View, {style: styles.row}, [
138+ h(View, {style: styles.avatarContainer}, [
139+ h(Image, {
140+ style: styles.avatar,
141+ source: avatarUrl,
142+ }),
143+ ]),
144+ h(View, {style: styles.authorColumn}, [
145+ authorNameText,
146+ h(Text, [timestampText, ' ' as any, msgTypeText]),
147+ ]),
148+ ]),
149+ ]),
150+ ]);
151+ }
152+}
src/app/components/RawFeed.tsView
@@ -1,0 +1,76 @@
1+/**
2+ * MMMMM is a mobile app for Secure Scuttlebutt networks
3+ *
4+ * Copyright (C) 2017 Andre 'Staltz' Medeiros
5+ *
6+ * This program is free software: you can redistribute it and/or modify
7+ * it under the terms of the GNU General Public License as published by
8+ * the Free Software Foundation, either version 3 of the License, or
9+ * (at your option) any later version.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import {PureComponent} from 'react';
21+import {View, StyleSheet} from 'react-native';
22+import {h} from '@cycle/react';
23+import ShortRawMessage from './messages/ShortRawMessage';
24+import {Palette} from '../global-styles/palette';
25+import {GetReadable, MsgAndExtras} from '../drivers/ssb';
26+import PullFlatList, {PullFlatListProps} from 'pull-flat-list';
27+import {withMutantProps} from 'react-mutant-hoc';
28+
29+const ShortRawMessageM = withMutantProps(ShortRawMessage, 'name', 'imageUrl');
30+
31+export const styles = StyleSheet.create({
32+ container: {
33+ alignSelf: 'stretch',
34+ flex: 1,
35+ },
36+
37+ itemSeparator: {
38+ backgroundColor: Palette.brand.voidBackground,
39+ height: 1,
40+ },
41+});
42+
43+class RawFeedItemSeparator extends PureComponent {
44+ public render() {
45+ return h(View, {style: styles.itemSeparator});
46+ }
47+}
48+
49+type Props = {
50+ getReadable: GetReadable<MsgAndExtras> | null;
51+ style?: any;
52+};
53+
54+export default class Feed extends PureComponent<Props, {}> {
55+ public render() {
56+ const {style, getReadable} = this.props;
57+
58+ return h<PullFlatListProps<MsgAndExtras>>(PullFlatList, {
59+ getScrollStream: getReadable,
60+ keyExtractor: (msg: MsgAndExtras, idx: number) => msg.key || String(idx),
61+ style: [styles.container, style] as any,
62+ initialNumToRender: 2,
63+ pullAmount: 3,
64+ numColumns: 1,
65+ refreshable: true,
66+ refreshColors: [Palette.indigo7],
67+ ItemSeparatorComponent: RawFeedItemSeparator,
68+ renderItem: ({item}) =>
69+ h(ShortRawMessageM, {
70+ msg: item,
71+ name: item.value._streams.about.name,
72+ imageUrl: item.value._streams.about.imageUrl,
73+ }),
74+ });
75+ }
76+}
src/app/drivers/ssb.tsView
@@ -78,17 +78,20 @@
7878 }
7979
8080 function mutateThreadWithLiveExtras(api: any) {
8181 return (thread: ThreadData) => {
82- thread.messages.forEach(msg => mutateMsgWithLiveExtras(api)(msg));
82+ for (const msg of thread.messages) {
83+ mutateMsgWithLiveExtras(api)(msg);
84+ }
8385 return thread;
8486 };
8587 }
8688
8789 export type GetReadable<T> = (opts?: any) => Readable<T>;
8890
8991 export class SSBSource {
9092 public selfFeedId$: Stream<FeedId>;
93+ public publicRawFeed$: Stream<GetReadable<MsgAndExtras>>;
9194 public publicFeed$: Stream<GetReadable<ThreadAndExtras>>;
9295 public publicLiveUpdates$: Stream<null>;
9396 public selfRoots$: Stream<GetReadable<ThreadAndExtras>>;
9497 public selfReplies$: Stream<GetReadable<MsgAndExtras>>;
@@ -97,8 +100,15 @@
97100
98101 constructor(private api$: Stream<any>) {
99102 this.selfFeedId$ = api$.map(api => api.keys.sync.id[0]());
100103
104+ this.publicRawFeed$ = api$.map(api => (opts?: any) =>
105+ pull(
106+ api.sbot.pull.feed[0]({reverse: true, live: false, ...opts}),
107+ pull.map(mutateMsgWithLiveExtras(api)),
108+ ),
109+ );
110+
101111 this.publicFeed$ = api$.map(api => (opts?: any) =>
102112 pull(
103113 api.sbot.pull.publicThreads[0]({reverse: true, live: false, ...opts}),
104114 pull.map(mutateThreadWithLiveExtras(api)),
src/app/index.tsView
@@ -23,8 +23,9 @@
2323 Compose = 'mmmmm.Compose',
2424 Thread = 'mmmmm.Thread',
2525 Profile = 'mmmmm.Profile',
2626 ProfileEdit = 'mmmmm.Profile.Edit',
27+ RawDatabase = 'mmmmm.RawDatabase',
2728 }
2829
2930 import onionify from 'cycle-onionify';
3031 import {makeKeyboardDriver} from 'cycle-native-keyboard';
@@ -38,8 +39,9 @@
3839 import {compose} from './screens/compose/index';
3940 import {thread} from './screens/thread/index';
4041 import {profile} from './screens/profile/index';
4142 import {editProfile} from './screens/profile-edit/index';
43+import {rawDatabase} from './screens/raw-db/index';
4244 import {Palette} from './global-styles/palette';
4345 import {Typography} from './global-styles/typography';
4446 import {addDisclaimer} from './alpha-disclaimer';
4547
@@ -49,8 +51,9 @@
4951 [Screens.Compose]: onionify(compose),
5052 [Screens.Thread]: addDisclaimer(onionify(thread)),
5153 [Screens.Profile]: addDisclaimer(onionify(profile)),
5254 [Screens.ProfileEdit]: addDisclaimer(onionify(editProfile)),
55+ [Screens.RawDatabase]: addDisclaimer(rawDatabase),
5356 };
5457
5558 export const drivers = {
5659 alert: alertDriver,
src/app/screens/drawer/intent.tsView
@@ -25,6 +25,8 @@
2525
2626 openAbout$: source.select('about').events('press').mapTo(null),
2727
2828 emailBugReport$: source.select('bug-report').events('press').mapTo(null),
29+
30+ showRawDatabase$: source.select('raw-db').events('press').mapTo(null),
2931 };
3032 }
src/app/screens/drawer/navigation.tsView
@@ -18,36 +18,26 @@
1818 */
1919
2020 import xs, {Stream} from 'xstream';
2121 import sample from 'xstream-sample';
22-import {Command} from 'cycle-native-navigation';
22+import {Command, PushCommand} from 'cycle-native-navigation';
2323 import {navOptions as profileScreenNavOptions} from '../profile';
24+import {navOptions as rawDatabaseScreenNavOptions} from '../raw-db';
2425 import {State} from './model';
2526 import {Screens} from '../..';
2627
2728 export type Actions = {
2829 goToSelfProfile$: Stream<null>;
30+ showRawDatabase$: Stream<null>;
2931 };
3032
3133 export default function navigationCommands(
3234 actions: Actions,
3335 state$: Stream<State>,
3436 ): Stream<Command> {
35- const toSelfProfile$ = actions.goToSelfProfile$
36- .compose(sample(state$))
37- .map(state => {
38- const hideDrawer: Command = {
39- type: 'mergeOptions',
40- opts: {
41- sideMenu: {
42- left: {
43- visible: false,
44- },
45- },
46- },
47- };
48-
49- const openSelfProfile: Command = {
37+ const toSelfProfile$ = actions.goToSelfProfile$.compose(sample(state$)).map(
38+ state =>
39+ ({
5040 type: 'push',
5141 id: 'mainstack',
5242 layout: {
5343 component: {
@@ -58,12 +48,41 @@
5848 },
5949 options: profileScreenNavOptions,
6050 },
6151 },
52+ } as PushCommand),
53+ );
54+
55+ const toRawDatabase$ = actions.showRawDatabase$.map(
56+ () =>
57+ ({
58+ type: 'push',
59+ id: 'mainstack',
60+ layout: {
61+ component: {
62+ name: Screens.RawDatabase,
63+ options: rawDatabaseScreenNavOptions,
64+ },
65+ },
66+ } as PushCommand),
67+ );
68+
69+ const hideDrawerAndPush$ = xs
70+ .merge(toSelfProfile$, toRawDatabase$)
71+ .map(pushCommand => {
72+ const hideDrawer: Command = {
73+ type: 'mergeOptions',
74+ opts: {
75+ sideMenu: {
76+ left: {
77+ visible: false,
78+ },
79+ },
80+ },
6281 };
6382
64- return xs.of<Command>(hideDrawer, openSelfProfile);
83+ return xs.of<Command>(hideDrawer, pushCommand);
6584 })
6685 .flatten();
6786
68- return toSelfProfile$;
87+ return hideDrawerAndPush$;
6988 }
src/app/screens/drawer/view.tsView
@@ -64,10 +64,16 @@
6464 text: 'Email bug report',
6565 accessible: true,
6666 accessibilityLabel: 'Email bug report',
6767 }),
68- h(DrawerMenuItem, {icon: 'database', text: 'Raw database'}),
6968 h(DrawerMenuItem, {
69+ sel: 'raw-db',
70+ icon: 'database',
71+ text: 'Raw database',
72+ accessible: true,
73+ accessibilityLabel: 'Show Raw Database',
74+ }),
75+ h(DrawerMenuItem, {
7076 sel: 'about',
7177 icon: 'information',
7278 text: 'About MMMMM',
7379 accessible: true,
src/app/screens/raw-db/README.mdView
@@ -1,0 +1,1 @@
1+This Cycle.js component represents the screen where all SSB messages, regardless of type, are displayed in a scrolling view.
src/app/screens/raw-db/index.tsView
@@ -1,0 +1,66 @@
1+/**
2+ * MMMMM is a mobile app for Secure Scuttlebutt networks
3+ *
4+ * Copyright (C) 2017 Andre 'Staltz' Medeiros
5+ *
6+ * This program is free software: you can redistribute it and/or modify
7+ * it under the terms of the GNU General Public License as published by
8+ * the Free Software Foundation, either version 3 of the License, or
9+ * (at your option) any later version.
10+ *
11+ * This program is distributed in the hope that it will be useful,
12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+ * GNU General Public License for more details.
15+ *
16+ * You should have received a copy of the GNU General Public License
17+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
18+ */
19+
20+import {Stream} from 'xstream';
21+import {Command, PopCommand, NavSource} from 'cycle-native-navigation';
22+import {SSBSource} from '../../drivers/ssb';
23+import {ReactSource, h} from '@cycle/react';
24+import {ReactElement} from 'react';
25+import {Dimensions} from '../../global-styles/dimens';
26+import RawFeed from '../../components/RawFeed';
27+
28+export type Sources = {
29+ screen: ReactSource;
30+ navigation: NavSource;
31+ ssb: SSBSource;
32+};
33+
34+export type Sinks = {
35+ screen: Stream<ReactElement<any>>;
36+ navigation: Stream<Command>;
37+};
38+
39+export const navOptions = {
40+ topBar: {
41+ height: Dimensions.toolbarAndroidHeight,
42+ title: {
43+ text: 'Raw database',
44+ },
45+ backButton: {
46+ icon: require('../../../../images/icon-arrow-left.png'),
47+ visible: true,
48+ },
49+ },
50+};
51+
52+export function rawDatabase(sources: Sources): Sinks {
53+ const vdom$ = sources.ssb.publicRawFeed$.map(getReadable =>
54+ h(RawFeed, {getReadable}),
55+ );
56+ const command$ = sources.navigation.backPress().mapTo(
57+ {
58+ type: 'pop',
59+ } as PopCommand,
60+ );
61+
62+ return {
63+ screen: vdom$,
64+ navigation: command$,
65+ };
66+}
src/ssb/opinions/sbot.tsView
@@ -228,15 +228,9 @@
228228 ...opts,
229229 });
230230 }),
231231 feed: rec.source((opts: any) => {
232- return pull(
233- pullMore(sbot.createFeedStream, {...opts, limit: 10}, [
234- 'value',
235- 'timestamp',
236- ]),
237- pull.through(runHooks),
238- );
232+ return sbot.createFeedStream(opts);
239233 }),
240234 log: rec.source((opts: any) => {
241235 return pull(sbot.createLogStream(opts), pull.through(runHooks));
242236 }),

Built with git-ssb-web