Compare commits

...
Sign in to create a new pull request.

325 commits

Author SHA1 Message Date
Akinwale Ariwodola
aa5dd878c0 fix displayed wallet custody message when signed in 2020-03-31 21:17:21 +01:00
Akinwale Ariwodola
47c6fb5889 update app strings 2020-03-26 15:46:33 +01:00
Akinwale Ariwodola
89a9571ce1
Display USD ()
* show usd values for lbc on wallet and rewards pages
* link to publish page
* update phrasing
* don't show rewards nag if user is not interested
2020-03-26 15:13:56 +01:00
Akinwale Ariwodola
689e30a5f0
display lbry.tv sync custody message ()
* display lbry.tv sync custody message
* rephrase text
2020-03-24 16:17:33 +01:00
Akinwale Ariwodola
33b4806c84 add values to liteFile state 2020-03-23 17:22:29 +01:00
Akinwale Ariwodola
7e1794ce29 fix publishes link. change minimum bid to 0.01. 2020-03-23 09:42:32 +01:00
Akinwale Ariwodola
4c4d561f30 new_android reward 2020-03-20 17:00:18 +01:00
Akinwale Ariwodola
a5953dcef0
Repost creation ()
* create reposts
* display reposts properly on publishes page
* show/hide advanced link
* fix edit mode on owned publishes
2020-03-20 14:21:18 +01:00
Akinwale Ariwodola
d33ed55bdf add gitlab CI for multi-project pipeline 2020-03-20 08:36:04 +01:00
Akinwale Ariwodola
12d7fbd472
lbry.tv hybrid mode ()
* use lbry.tv for resolve and claim_search
* gracefully handle dynamic sdk ready state
* update pinned lbry-redux commit
* use empty state view when sdk is not ready
* traditional mode if dht is enabled
2020-03-20 08:25:39 +01:00
Akinwale Ariwodola
4645a8f412 update pinned lbryinc commit 2020-03-17 22:17:40 +01:00
Akinwale Ariwodola
fda9dbc606
Merge pull request from lbryio/fix-related-content
fix related content
2020-03-16 18:47:56 +01:00
Akinwale Ariwodola
f26f1fe215 fix related content 2020-03-13 17:37:44 +01:00
Akinwale Ariwodola
dabba98c0c
Add DHT setting ()
* dht setting
* update setting text
2020-03-13 17:05:29 +01:00
YULIUS KURNIAWAN KRISTIANTO
08c6cf1b7f
added more languages support ()
12 new languages
* Danish
* Basa Jawa
* Ukrainian
* Hindi
* Dutch
* French
* Estonian
* Marathi
* Russian
* Romanian
* Slovak
* Ukrainian
2020-03-10 22:09:06 +01:00
Akinwale Ariwodola
195d155c70
Fast lite mode ()
* added lite file page
* update splash logic
* updates to navigation handling for lite mode
* update packages
* show firebase token on About page
* handle additional params for notifiication cold start
* handle opening urls in lite mode
2020-03-10 22:08:17 +01:00
Akinwale Ariwodola
30f66d23de
Merge pull request from lbryio/reposts-display
display reposts information
2020-03-02 18:03:58 +01:00
Akinwale Ariwodola
3c5dfc9899 display reposts information 2020-03-02 17:41:01 +01:00
Akinwale Ariwodola
6392dbe975 readme: lbrynet-daemon -> LBRY SDK 2020-03-02 16:45:44 +01:00
Akinwale Ariwodola
485e62725c sample google-services.json 2020-03-02 16:41:37 +01:00
Akinwale Ariwodola
8a2b0b9bc1 react-native-cli install 2020-03-02 16:23:53 +01:00
Akinwale Ariwodola
e372f1ead4 update readme with additional notes 2020-03-02 16:13:19 +01:00
Akinwale Ariwodola
9582b7ad7d update readme 2020-03-02 16:08:58 +01:00
Akinwale Ariwodola
f8de4cbc1a android folder submodule 2020-03-01 21:27:40 +01:00
Akinwale Ariwodola
82508559b9 remove android folder for submodule creation 2020-03-01 21:20:23 +01:00
Akinwale Ariwodola
e1f4994990
New build ()
* new project structure using android aar
* remove blockchain/headers file
* rename ios files
* add android release script
2020-02-27 09:05:00 +01:00
Akinwale Ariwodola
19d3d8cd94 display sign in message on following page 2020-02-25 19:39:26 +01:00
Akinwale Ariwodola
bcfb65ed54 fix suggested subscriptions state handling 2020-02-25 19:35:21 +01:00
Akinwale Ariwodola
0536f0e4bf assign existing subscriptions to not_channel_ids 2020-02-24 20:26:36 +01:00
Akinwale Ariwodola
0db2c7e628 basic repost redirect handling 2020-02-24 15:55:44 +01:00
Akinwale Ariwodola
f05d357fa7
Following rework ()
* make following the default page. add suggested grd.
* suggested grid vertical scrolling. semi-infinite scroll
* search and related content fixes
2020-02-24 15:20:03 +01:00
Akinwale Ariwodola
1f649b9d38
App timing ()
* log launch timing. recalculate play time to start timing.
* remove decimal points from time to start values
2020-02-17 07:24:39 +01:00
Akinwale Ariwodola
55b292ab0e
Merge pull request from lbryio/wallet-balance-extra
add wallet balance extra details card
2020-02-17 06:15:20 +01:00
Akinwale Ariwodola
736de8e5d9
Merge pull request from lbryio/invite-no-channels
Invite link with no channels
2020-02-17 06:15:09 +01:00
Akinwale Ariwodola
f06252d397
Merge pull request from lbryio/invite-channel-endpoint
call publish endpoint with selected channel on invites page
2020-02-17 06:14:58 +01:00
Akinwale Ariwodola
fa25789855
Merge pull request from lbryio/invites-canonical-urls
canonical urls for invite links
2020-02-17 06:14:46 +01:00
Akinwale Ariwodola
514e077fbb
Merge pull request from lbryio/back-navigation-tweaks
back navigation fixes and tweaks
2020-02-17 06:14:32 +01:00
Akinwale Ariwodola
3d44bfa88b add wallet balance extra details card 2020-02-10 15:27:54 +01:00
Akinwale Ariwodola
f81e2d7441 generate default invite link if no channels are present 2020-02-10 12:54:35 +01:00
Akinwale Ariwodola
81bdd37576 call publish endpoint with selected channel on invites page 2020-02-10 12:38:20 +01:00
Akinwale Ariwodola
76938d3349 canonical urls for invite links 2020-02-10 12:25:20 +01:00
Akinwale Ariwodola
456c787683 fix channel creator and publish back navigation to corresponding claims. 2020-02-06 21:23:05 +01:00
Akinwale Ariwodola
3a5edc461a
Merge pull request from michaeltintiuc/audio-focus
Fixes . Listen to audio focus change
2020-02-04 05:12:15 +01:00
Thomas Zarebczan
3a8af2d7a0
Merge pull request from ykris45/patch-1
Update LICENSE
2020-02-03 17:04:55 -05:00
Akinwale Ariwodola
601c589f69 fix weird case where splash screen gets stuck on authenticating 2020-02-03 09:20:48 +01:00
Akinwale Ariwodola
f5ae1f34f7 resolved search nsfw 2020-02-03 07:27:07 +01:00
YULIUS KURNIAWAN KRISTIANTO
4656be64a7
Update LICENSE
update year
2020-02-03 04:40:41 +07:00
Michael Tintiuc
8e377d0e74 Fixes . Listen to audio focus change 2020-02-01 15:44:04 +02:00
Akinwale Ariwodola
bf7f836465 fix search restore and background play bug 2020-01-31 08:52:07 +01:00
Akinwale Ariwodola
017787ef25
optimise file page renders () 2020-01-29 12:55:04 +01:00
Akinwale Ariwodola
d9ada93ff3
Invites page () 2020-01-29 12:25:23 +01:00
Akinwale Ariwodola
95a33b411b back navigation fixes and tweaks 2020-01-24 13:47:41 +01:00
Akinwale Ariwodola
9bc5a2ecac fix thumbnailUrl check 2020-01-23 02:15:39 +01:00
Akinwale Ariwodola
4e6267d5a9 additional thumbnailUrl check 2020-01-23 00:55:45 +01:00
Akinwale Ariwodola
dc0b06ce55 fix additional channel creator edit error 2020-01-22 21:21:01 +01:00
Akinwale Ariwodola
82d15f0c7f fix channel creator edit error 2020-01-22 18:39:47 +01:00
Akinwale Ariwodola
9f32363a09 fix existing publish name check for edit mode 2020-01-22 02:05:50 +01:00
Akinwale Ariwodola
467272d5c8 tweak file page style 2020-01-22 02:01:06 +01:00
Akinwale Ariwodola
3594b70113 update app strings 2020-01-21 06:47:49 +01:00
Akinwale Ariwodola
5f008b3ac9 download confirmation dialog 2020-01-20 23:36:10 +01:00
Akinwale Ariwodola
3f21c4e08c display circular download progress on file page 2020-01-20 23:11:14 +01:00
Akinwale Ariwodola
3717a8072b error on publish to existing content address 2020-01-20 21:52:59 +01:00
Akinwale Ariwodola
46bfbd242a
Navigate to the last visited route ()
* navigate to the last visited route on app startup if there is no launch url
* update eslint no-console rule
2020-01-20 21:40:17 +01:00
Akinwale Ariwodola
0361b10575 add onDownloadAborted event handler 2020-01-20 21:33:17 +01:00
Akinwale Ariwodola
b9dec86cae add 10-second seek controls to media player 2020-01-20 18:18:01 +01:00
Akinwale Ariwodola
2465b5514f remove download notification if file is deleted from Library page 2020-01-20 17:58:48 +01:00
Akinwale Ariwodola
df3a9587c0 update fileGetStarted logic 2020-01-17 06:43:02 +01:00
Akinwale Ariwodola
5df8b8c4aa
Playable downloads ()
* enable download button for video and audio content
* fix media player source and button display
* show notifications for video downloads. fix double playback.
* fix ongoing playback when back button pressed
* fix double-playback when navigating to related content links
* update url for stop download
2020-01-16 22:00:34 +01:00
Akinwale Ariwodola
156e310368 fix error on opening related content or urls 2020-01-14 02:29:30 +01:00
Akinwale Ariwodola
683b1f4805 replace file page in navigation using the same key 2020-01-13 16:17:18 +01:00
Akinwale Ariwodola
e263ad7569 react navigation drawer workaround 2020-01-13 12:24:03 +01:00
Akinwale Ariwodola
24d580ea13 remove calls to doCheckSubscriptionsInit 2020-01-10 23:41:05 +01:00
Akinwale Ariwodola
bfc3b11035 fix channel thumbnail container render. style tweaks for smaller screens. 2020-01-10 00:37:51 +01:00
Akinwale Ariwodola
6bf2f9460d fix claim result item channel render checks 2020-01-09 20:34:06 +01:00
Akinwale Ariwodola
504baeefaf fullscreen media player restore tweak 2020-01-09 18:56:36 +01:00
Akinwale Ariwodola
6c55633751 use Inter font for markdown display and some style tweaks 2020-01-09 18:11:23 +01:00
Akinwale Ariwodola
3395fa7aa2 fix storage permission request flow on new publish page 2020-01-09 17:45:02 +01:00
Akinwale Ariwodola
7a3def7ea1 fix claim result channel thumbnail view 2020-01-09 17:35:10 +01:00
Akinwale Ariwodola
f1aafd6e34
unified display of channel results ()
* unified display of channel results
* add autoStyle to thumbnail container
* move common shared methods to helper
* change Image to FastImage in list views
2020-01-09 17:19:30 +01:00
Akinwale Ariwodola
1ce5fad8c0
allow scrolling for displaying more search results ()
* allow scrolling for displaying more search results
* add search page size constant
* use default page size. fix typo.
2020-01-09 16:49:14 +01:00
Akinwale Ariwodola
4690451bfe change default page size to 20 2020-01-09 16:46:08 +01:00
Akinwale Ariwodola
b149c0039a update fontFamily definitions 2020-01-09 15:43:36 +01:00
Akinwale Ariwodola
e97bf7ce79 cleanup: remove unused function 2020-01-09 04:43:09 +01:00
Akinwale Ariwodola
8c79115ce0 update input cursor position on press when the uri bar focused 2020-01-09 04:42:06 +01:00
Akinwale Ariwodola
24fa80d92a open links displayed in the embedded webview on the device browser 2020-01-09 04:29:48 +01:00
Akinwale Ariwodola
624d95ab42
Merge pull request from lbryio/better-markdown-display
improve markdown display
2020-01-08 22:03:38 +01:00
Akinwale Ariwodola
8519b561b3 improve markdown display 2020-01-08 21:23:02 +01:00
Akinwale Ariwodola
00dfc205a5 readjust margin for view count 2020-01-08 12:08:32 +01:00
Akinwale Ariwodola
b6ab92a700 adjust margins for view count and floating wallet balance 2020-01-08 12:06:06 +01:00
Akinwale Ariwodola
561aa0db9c placeholder background for thumbnails being loaded 2020-01-08 10:17:52 +01:00
Akinwale Ariwodola
ad9056f91a fix layout after restoring from fullscreen media player 2020-01-07 22:59:18 +01:00
Akinwale Ariwodola
bc62580aae downgrade drawer navigator because it's still being weird 2020-01-07 11:21:01 +01:00
Akinwale Ariwodola
f5cb8e44d1 display open file button. code cleanup. 2020-01-07 08:47:29 +01:00
Akinwale Ariwodola
c47c7cd925 basic markdown rendering 2020-01-07 08:34:20 +01:00
Akinwale Ariwodola
b25f913921 fix country selector on phone verification page 2020-01-07 02:36:42 +01:00
Akinwale Ariwodola
673d6dc0f5 update tag result if the search keyword is updated 2020-01-06 23:01:27 +01:00
Akinwale Ariwodola
91eb18ec9b fix error message displayed repeatedly for content with fileInfo 2020-01-06 21:49:22 +01:00
Akinwale Ariwodola
4ed3809ffd final fix for uri bar and some performance improvements 2020-01-06 21:11:15 +01:00
Akinwale Ariwodola
3f0ed7988a Merge remote-tracking branch 'origin/resolved-search' 2020-01-05 19:02:25 +01:00
Akinwale Ariwodola
b3e20e78cb fix uri bar text input selection. update react navigation packages. 2020-01-05 19:01:31 +01:00
Akinwale Ariwodola
331f3d58ee remove console.log 2020-01-05 18:12:03 +01:00
Akinwale Ariwodola
3b0ea191fe display duration, fees and obscure nsfw for resolved search results 2020-01-05 18:07:28 +01:00
Akinwale Ariwodola
20a984f259 display resolved search results 2020-01-05 13:56:09 +01:00
Akinwale Ariwodola
2d91c83628 better fix for uri bar text input handling 2020-01-05 11:44:05 +01:00
Akinwale Ariwodola
d7635dc7ee update doSearch calls 2020-01-05 11:40:38 +01:00
Akinwale Ariwodola
13136e606d fix uri bar text input handling 2020-01-05 11:24:03 +01:00
Akinwale Ariwodola
5ae5144eda file price styling changes 2020-01-05 11:11:02 +01:00
Akinwale Ariwodola
3e81d4ef8d check storage permission for channel creator and publish pages 2020-01-04 07:11:06 +01:00
Akinwale Ariwodola
8365fa3fa0
Merge pull request from lbryio/patch-release
updates for patch release
2020-01-03 18:53:18 +01:00
Akinwale Ariwodola
5681630e4c updates for patch release 2020-01-03 18:51:39 +01:00
Akinwale Ariwodola
1eecae469d rename Subscriptions -> Following. fix bad subs. 2019-12-29 14:56:15 +01:00
Akinwale Ariwodola
f7c8bf3949 fix subsequent searches on search page. 2019-12-29 11:10:33 +01:00
Akinwale Ariwodola
d538838921 hook reactotron into redux. more search page tweaks. 2019-12-29 11:02:11 +01:00
Akinwale Ariwodola
b5abddb69a
Merge pull request from lbryio/reactotron
add reactotron. fix batch resolve functionality for search results.
2019-12-29 10:35:13 +01:00
Akinwale Ariwodola
15cbdcfaca add reactotron. fix batch resolve functionality for search results. 2019-12-29 10:34:00 +01:00
Akinwale Ariwodola
30246a189c remove console.log 2019-12-29 07:23:42 +01:00
Akinwale Ariwodola
f77f0a7aa2 search tweaks and package updates 2019-12-29 06:48:13 +01:00
Akinwale Ariwodola
36d4bd7e99
Merge pull request from lbryio/navigation-drawer-test
update react-navigation-drawer package
2019-12-29 06:45:47 +01:00
Akinwale Ariwodola
b8f2a9e24f tweak related content view for perf. fix displayed dates. 2019-12-28 21:02:29 +01:00
Akinwale Ariwodola
d7edbf55b5 react-navigation-drawer 2.3.3 2019-12-28 19:36:17 +01:00
Akinwale Ariwodola
d362d9e8dd remove console.log 2019-12-28 15:56:37 +01:00
Akinwale Ariwodola
f47693fb35
First run changes ()
* streamlined first run
* download button tweaks
* fix downloads. file page tweaks.
2019-12-28 15:45:12 +01:00
Akinwale Ariwodola
54b10c818e
Merge pull request from lbryio/react-native-0.61
update to RN 0.61.5
2019-12-28 15:16:56 +01:00
Akinwale Ariwodola
4b283e6606 update to RN 0.61.5 2019-12-28 15:14:37 +01:00
Akinwale Ariwodola
67723813cc add semi-transparent background to channel name and follower count 2019-12-20 18:53:53 +01:00
Akinwale Ariwodola
80b12a2cc8 updates to modal tip view 2019-12-20 09:01:26 +01:00
Akinwale Ariwodola
48ce7e9d9e pin lbry-redux and lbryinc commits 2019-12-20 08:46:44 +01:00
Akinwale Ariwodola
696ff616f7
Merge pull request from lbryio/snackbars
use snackbars instead of toasts
2019-12-18 16:49:18 +01:00
Akinwale Ariwodola
94050e5e6d fix i18n and update app strings 2019-12-18 12:34:33 +01:00
Akinwale Ariwodola
d52436e8d4 fix possible trim error on first run email entry 2019-12-18 12:16:34 +01:00
Akinwale Ariwodola
f231741789 use snackbars instead of toasts 2019-12-18 11:47:56 +01:00
Akinwale Ariwodola
dd7f1e82ba
tip button on channels, modal send tip component ()
* tip button on channels, modal send tip component
* add tip modal to file view
2019-12-18 11:01:33 +01:00
Akinwale Ariwodola
17b381de71
display follower and view counts () 2019-12-18 10:30:07 +01:00
Akinwale Ariwodola
53f143f232
remove subscriptions with short channel URLs ()
* remove subscriptions with short channel URLs
* added comments
2019-12-18 10:25:27 +01:00
Akinwale Ariwodola
c58aecb35e
display balance when LBC input text is focused ()
* display balance when LBC input text is focused
* use coins icon instead of 'Bal:' text
2019-12-18 10:22:33 +01:00
Akinwale Ariwodola
8b4f1de951
Merge pull request from lbryio/play-press-area
enable pressing the entire media container area to play or download content
2019-12-13 10:31:28 +01:00
Akinwale Ariwodola
daa4f5c5dc enable pressing the entire media container area to play or download content 2019-12-13 10:29:07 +01:00
Akinwale Ariwodola
6ae9632fb9
fixes before release ()
* fixes before next release
* make channel sort by selection component-specific
2019-12-11 09:06:10 +01:00
Akinwale Ariwodola
c33cf47397 add Arabic language setting 2019-12-06 19:33:26 +01:00
Akinwale Ariwodola
e0fa80f640 i18n reward intro driver title 2019-12-06 19:21:06 +01:00
Akinwale Ariwodola
0e65d526d8 fix header sync progress value 2019-12-06 19:13:47 +01:00
Akinwale Ariwodola
86e5bcf7e0 app strings 2019-12-06 17:55:10 +01:00
Akinwale Ariwodola
e9858f85e8
Merge pull request from lbryio/i18n-strings
i18n more strings
2019-12-06 17:54:28 +01:00
Akinwale Ariwodola
0b43454adc i18n more strings 2019-12-06 17:51:15 +01:00
Akinwale Ariwodola
d69c1931ac
move file action buttons to large button row ()
* move file action buttons to large button row
* update button order
2019-12-06 17:22:42 +01:00
Akinwale Ariwodola
970b53ffdc
Merge pull request from lbryio/fix-password-flow
fix error for auto-login and remove password warning
2019-12-06 16:54:53 +01:00
Akinwale Ariwodola
372a572ee6 fix error for auto-login and remove password warning 2019-12-06 16:53:55 +01:00
Akinwale Ariwodola
01eb112888
Merge pull request from lbryio/headers-sync
sdk 0.48 headers synchronization progress
2019-12-06 16:41:07 +01:00
Akinwale Ariwodola
bb9e990f5a sdk 0.48 headers synchronization progress 2019-12-06 16:40:17 +01:00
Akinwale Ariwodola
3237652d35
update text for signed in users ()
* update text for signed in users
* i18n tweaks
2019-12-06 16:01:25 +01:00
Akinwale Ariwodola
fd715886cd
display sign in instructions for emails that exist ()
* display sign in instructions for emails that exist
* lbry.tv account
2019-12-05 19:30:23 +01:00
Akinwale Ariwodola
fa2de8382a
Merge pull request from lbryio/sdk-0.48
pin lbry-redux commit
2019-12-05 13:41:07 +01:00
Akinwale Ariwodola
f03a88ab2a pin lbry-redux commit 2019-12-05 13:24:22 +01:00
Akinwale Ariwodola
184d5d1ec3
i18n ()
* i18n all the strings
* assign file content
* download and save language files
* load language on startup
* load language setting correctly when app launches
* fix i18n calls in constants
* pin lbryinc commit
2019-12-05 13:16:43 +01:00
Akinwale Ariwodola
1a190fb0fe do not display notification bell button 2019-12-03 15:07:40 +01:00
Akinwale Ariwodola
5e67cbe9ce
Password flow ()
* do not prompt new users for a password
* auto-login when no password is set
2019-12-03 15:02:34 +01:00
Akinwale Ariwodola
bc04f7105b
Merge pull request from lbryio/trending
fix: trending global > group
2019-11-25 11:17:38 +01:00
Akinwale Ariwodola
13fd9002fa
Merge pull request from lbryio/auto-fetch-images
auto-fetch image and text types
2019-11-25 11:16:28 +01:00
Akinwale Ariwodola
fe5462971e auto-fetch image and text types 2019-11-22 02:56:16 +01:00
Thomas Zarebczan
89dff3ace3
fix: trending global > group
Trending mixed already takes into account the global score. This improves results a bit and is now indexed on the wallet server. Desktop was updated to match also.
2019-11-21 09:50:48 -05:00
Akinwale Ariwodola
01c3bc489b
Merge pull request from lbryio/channel-page-newest-first
default sort content on channel view by newest first
2019-11-20 01:01:52 +01:00
Akinwale Ariwodola
e208a9960f default sort content on channel view by newest first 2019-11-20 00:59:05 +01:00
Akinwale Ariwodola
e5c6270d5c
Merge pull request from lbryio/first-run-navigation
always navigate to first run if not completed
2019-11-18 06:52:04 +01:00
Akinwale Ariwodola
afbb9e1d79
add fullscreen mode to app state to better handle suspend / resume ()
* add fullscreen mode to app state to better handle suspend / resume
* rename argument mode -> isFullscreen
2019-11-18 06:50:55 +01:00
Akinwale Ariwodola
357be73947
refactor and batch-resolve for related content component ()
* refactor and batch-resolve for related content component
* code cleanup
2019-11-18 06:48:54 +01:00
Akinwale Ariwodola
2ddfc1eaf8 always navigate to first run if not completed 2019-11-14 23:07:28 +01:00
Akinwale Ariwodola
e901e81074 fetching cost info fix 2019-11-12 21:10:23 +01:00
Akinwale Ariwodola
2e3e1a1250
Merge pull request from lbryio/sync-after-signin
restore user preferences after in-app sign in  flow
2019-11-12 15:35:44 +01:00
Akinwale Ariwodola
190af405bb
Merge pull request from lbryio/faster-short-urls
avoid re-resolving a claim when navigation to a short / canonical URL
2019-11-12 15:35:28 +01:00
Akinwale Ariwodola
4467853d44
special URLs for all top-level navigation routes ()
* special URLs for all top-level navigation routes
* rename yourTags to discover
2019-11-11 15:49:27 +01:00
Akinwale Ariwodola
a45f68d096 avoid re-resolving a claim when navigation to a short / canonical URL 2019-11-08 07:13:24 +01:00
Akinwale Ariwodola
79686ff6b9 restore user preferences after in-app sign in flow 2019-11-07 17:13:37 +01:00
Akinwale Ariwodola
76305a67be
Tag search result ()
* show tag search result
* run claim search for tag result
2019-11-07 16:55:04 +01:00
Akinwale Ariwodola
82345070fb
Merge pull request from lbryio/better-file-handling
display unsupported content view
2019-11-07 16:51:56 +01:00
Akinwale Ariwodola
56aa0a96a7
Merge pull request from lbryio/wallet-send
disable send button if the recipient address is not valid
2019-11-06 13:42:19 +01:00
Akinwale Ariwodola
232cdb3d29 disable send button if the recipient address is not valid 2019-11-06 13:41:05 +01:00
Akinwale Ariwodola
61189b817a display unsupported content view 2019-11-06 13:26:39 +01:00
Akinwale Ariwodola
a127e14a0f
Merge pull request from lbryio/fcm-data
show notification settings. handle blackListedOutpoints being null.
2019-11-01 06:54:02 +01:00
Akinwale Ariwodola
51e3dfcf6a show notification settings. handle blackListedOutpoints being null. 2019-11-01 06:52:39 +01:00
Akinwale Ariwodola
5915919560 hide notification settings 2019-10-31 17:34:32 +01:00
Akinwale Ariwodola
943c5654c1 cleanup console.log 2019-10-31 07:54:42 +01:00
Akinwale Ariwodola
117c23fe3f
Merge pull request from lbryio/launch-url-fix
fix for launch urls and notification target urls
2019-10-31 07:53:59 +01:00
Akinwale Ariwodola
072ad25146 fix for launch urls and notification target urls 2019-10-31 07:52:57 +01:00
Akinwale Ariwodola
f714310681 normalise tag name from search result 2019-10-30 13:02:33 +01:00
Akinwale Ariwodola
9bc5a1538a
RC ()
* show tag search result
* fix suggested subscriptions. reduce number of search results.
* run claim search for tag result
2019-10-30 10:35:43 +01:00
Akinwale Ariwodola
1458e4f321 capitalise menu item labels 2019-10-29 15:06:37 +01:00
Akinwale Ariwodola
ed513bcfb5 fix learn more link style 2019-10-29 15:02:54 +01:00
Akinwale Ariwodola
518285355c
Merge pull request from lbryio/tos
terms of service acknowledgement
2019-10-29 15:00:29 +01:00
Akinwale Ariwodola
030b94ce01 terms of service acknowledgement 2019-10-29 14:59:20 +01:00
Akinwale Ariwodola
12ca56cb45 change learn more link colour 2019-10-29 14:54:19 +01:00
Akinwale Ariwodola
8eb661171d tweak sign in button 2019-10-29 14:52:59 +01:00
Akinwale Ariwodola
67349b0ef6
add sign in area to drawer content ()
* add sign in area to drawer content
* make sign in a menu item in the drawer content
* fix typo
2019-10-29 14:04:21 +01:00
Akinwale Ariwodola
41fd14e18f
wallet sign in driver () 2019-10-29 13:36:11 +01:00
Akinwale Ariwodola
ce36be2b7b
Merge pull request from lbryio/channel-share
add share button to channel page
2019-10-29 13:33:30 +01:00
Akinwale Ariwodola
b4fc61ada0
Merge pull request from lbryio/wait-for-sync
wait for getSync to complete and apply user settings
2019-10-28 16:36:41 +01:00
Akinwale Ariwodola
032b5c26b1 wait for getSync to complete and apply user settings 2019-10-28 16:32:09 +01:00
Akinwale Ariwodola
871c2c538b
Merge pull request from lbryio/verified-sync
only sync for users with a verified email address
2019-10-25 16:54:42 +01:00
Akinwale Ariwodola
60f6645515 only sync for users with a verified email address 2019-10-25 16:53:40 +01:00
Akinwale Ariwodola
61f0aa5dac
add notification settings () 2019-10-25 16:44:49 +01:00
Akinwale Ariwodola
2c6445c61c
improve handling of pending claims () 2019-10-25 16:43:57 +01:00
Akinwale Ariwodola
3239174ddf
Merge pull request from lbryio/ux-updates
rewards page and rewards driver changes
2019-10-25 15:48:22 +01:00
Akinwale Ariwodola
9157b18f45 add share button to channel page 2019-10-25 15:24:57 +01:00
Akinwale Ariwodola
f09269d48a rewards page and rewards driver changes 2019-10-25 15:14:06 +01:00
Akinwale Ariwodola
69a760f011
Navigation drawer ()
* downgrade react-navigation-drawer package
* adjust drawer width
2019-10-21 18:22:03 +01:00
Akinwale Ariwodola
22cd556e13 fix blank password wallet state 2019-10-21 15:55:55 +01:00
Akinwale Ariwodola
68bee32dde
Merge pull request from lbryio/blank-password-encrypt
do not call wallet_encrypt with blank password
2019-10-20 19:31:15 +01:00
Akinwale Ariwodola
cc2b3b845d do not call wallet_encrypt with blank password 2019-10-20 19:30:18 +01:00
Akinwale Ariwodola
b6df5ee97c more cleanup 2019-10-17 20:14:04 +01:00
Akinwale Ariwodola
ddd8d49a62 cleanup 2019-10-17 20:12:43 +01:00
Akinwale Ariwodola
e6ee26f94c fix for devices without Firebase support 2019-10-17 19:55:30 +01:00
Akinwale Ariwodola
2bf67d31e8 proceed from splash if Firebase doesn't work 2019-10-17 19:12:16 +01:00
Akinwale Ariwodola
9ec5ac5d60 remove getSync wait before navigation to main 2019-10-17 18:19:45 +01:00
Akinwale Ariwodola
9080913811
Next RC ()
* use doPreferenceGet for retrieving saved user state
* add share button
* remove extra slash
* preferences sync and wallet sync updates
* preference updates. send firebase token.
* add URI bar suggestions setting. reorganise settings page
* NSFW -> mature
* sdk calls: account_* -> wallet_*
* more updates for release
* filtered homepage. other updates.
* log publish
* remove getSync from channel creation success
2019-10-17 14:10:49 +01:00
Akinwale Ariwodola
eca6af122b
improve ux for insufficient balance for channel creation and publish () 2019-10-15 17:56:22 +01:00
Akinwale Ariwodola
b258654d60
add share button ()
* add share button
* remove extra slash
* change beta.lbry.tv -> lbry.tv
2019-10-15 08:31:03 +01:00
Akinwale Ariwodola
342e022594
Merge pull request from lbryio/fix-dmca-report
Fix dmca report route
2019-10-11 14:11:02 +01:00
Thomas Zarebczan
d16e4b7f69
Fix dmca report route
example: https://lbry.com/dmca/116d62d14d8efc311e1f8a9de92dcbc17deb259a
2019-10-10 13:44:48 -04:00
Alex Grin
648206b6a5
Merge pull request from StrikerRUS/patch-1
bump year in license
2019-10-09 10:37:30 -04:00
Nikita Titov
14994d2a30
bump year in license 2019-10-08 23:36:02 +03:00
Akinwale Ariwodola
ab97be6a4d
Merge pull request from lbryio/backup-manually
Add note about backing up manually
2019-10-02 10:37:46 +01:00
Akinwale Ariwodola
e39f5907a8
Suggested subscriptions improvements ()
* check nsfw flag when retrieving trending channels
* rename 'Tags you follow' to 'Suggested channels'
* implement suggested subscriptions modal
2019-10-02 10:36:32 +01:00
Akinwale Ariwodola
c203aa914c
fix subscription notifications () 2019-10-02 10:35:34 +01:00
Akinwale Ariwodola
f0a3eec80b
Merge pull request from lbryio/report-button
report content button
2019-10-01 11:08:14 +01:00
Thomas Zarebczan
47bb996342
Add note about backing up manually
feedback from https://www.reddit.com/r/lbry/comments/dakri9/uninstall_stipulation_is_extreme_unsure_if_i_want/
2019-09-30 12:03:19 -04:00
Akinwale Ariwodola
aba0b860e8
show the camera after the required permissions have been granted () 2019-09-30 12:55:30 +01:00
Akinwale Ariwodola
b135ca36a3 report content button 2019-09-30 12:48:13 +01:00
Akinwale Ariwodola
46b99e769f fix channel creator edit mode 2019-09-27 00:49:44 +01:00
Akinwale Ariwodola
b5dad6b5fb code cleanup 2019-09-27 00:02:08 +01:00
Akinwale Ariwodola
31d4d46742 fix publishing 2019-09-27 00:00:09 +01:00
Akinwale Ariwodola
5d86b68a3e fix typo 2019-09-26 09:54:15 +01:00
Akinwale Ariwodola
62ac844084 channel creator fixes. fix wallet sync disable / re-enable errors. 2019-09-26 07:25:33 +01:00
Akinwale Ariwodola
eebb25dba0 fix channel creator canSave flag 2019-09-25 23:34:58 +01:00
Akinwale Ariwodola
88e450eb08 post-0.9.0 fixes 2019-09-25 23:21:39 +01:00
Akinwale Ariwodola
ca8670e23c fix text input error when channel list is empty 2019-09-25 19:23:27 +01:00
Akinwale Ariwodola
1b4c023c92 don't push your tags screen into the drawwer stack 2019-09-25 17:56:43 +01:00
Akinwale Ariwodola
9aabad56a2 eliminate final blank password headache 2019-09-25 16:37:16 +01:00
Akinwale Ariwodola
3051deb7c7 cleanup 2019-09-25 15:46:11 +01:00
Akinwale Ariwodola
f2f5cd9ad5 fix back navigation and channel name validation 2019-09-25 14:44:33 +01:00
Akinwale Ariwodola
f800f22277 fix: show delete button for owned claims 2019-09-24 18:58:51 +01:00
Akinwale Ariwodola
2558f0c392 remove redundant code 2019-09-24 17:27:46 +01:00
Akinwale Ariwodola
02d8107b50
Sync user settings () 2019-09-24 17:26:42 +01:00
Akinwale Ariwodola
5943a796e4
0.9.0 rc ()
* sync user settings
* fix errors and user shared state
* fix: publish button disabled for file uploads
* miscellaneous fixes. enable thumbnail selection on publish page.
* get user settings after verification. tweak sync verify.
* fix states and channel edit errors
* empty state views. fix publishing state.
* channel about tab empty state
2019-09-24 17:22:51 +01:00
Akinwale Ariwodola
ffc4d828a7
Merge pull request from lbryio/react-navigation
use react-navigation-stack module
2019-09-23 18:38:57 +01:00
Akinwale Ariwodola
23cf8b15bc use react-navigation-stack module 2019-09-23 17:47:30 +01:00
Akinwale Ariwodola
76cc8eaea1 update react-navigation-drawer package 2019-09-23 16:43:54 +01:00
Akinwale Ariwodola
f372b33f42
Publish UX ()
* persist publish form state
* better handling of pending form states
2019-09-20 10:29:37 +01:00
Akinwale Ariwodola
a26fb3e06b
fix channel selector issues on publish page ()
* fix channel selector issues on publish page
* remove future release warning
* fix channel creation error
2019-09-15 12:02:34 +01:00
Akinwale Ariwodola
8bd3bd21e8
Merge pull request from lbryio/reward-range
support reward range amount
2019-09-15 11:45:41 +01:00
Akinwale Ariwodola
0115025b86 support reward range amount 2019-09-13 08:27:41 +01:00
Akinwale Ariwodola
34fa24ffd0
Channel creation and editing () 2019-09-13 08:16:06 +01:00
Akinwale Ariwodola
9e8ba95fec
Merge pull request from lbryio/autoclaim-rewards
auto claim eligible rewards after viewing a file
2019-09-12 07:20:02 +01:00
Akinwale Ariwodola
d63253cc30 auto claim eligible rewards after viewing a file 2019-09-11 12:44:44 +01:00
Akinwale Ariwodola
6ce143b145
Merge pull request from lbryio/publish-currency
add currency selector on publish page
2019-09-11 09:07:34 +01:00
Akinwale Ariwodola
c7ce7ea284 add currency selector on publish page 2019-09-10 13:23:34 +01:00
Akinwale Ariwodola
076122def4
Merge pull request from lbryio/file-uploads
allow users to upload any file
2019-09-10 12:57:25 +01:00
Akinwale Ariwodola
625559528d allow users to upload any file 2019-09-09 16:26:50 +01:00
Akinwale Ariwodola
c88961002f
canonical url and transform updates ()
* canonical url and transform updates
* account unlock after sync apply
* fix featured search item
* unlock after sync apply
* pin lbryinc commit
* fix featured search result item stuck loading
2019-09-06 19:43:47 +01:00
Akinwale Ariwodola
decf0623fb
better featured search result item handling () 2019-09-05 15:57:27 +01:00
Akinwale Ariwodola
c7f2b78b92 fixes as per review 2019-09-05 15:53:25 +01:00
Akinwale Ariwodola
47cf15741c better featured search result item handling 2019-09-02 14:15:48 +01:00
Akinwale Ariwodola
e74a419ffd cleanup 2019-08-27 07:10:09 +01:00
Akinwale Ariwodola
0c06f478c8
Merge pull request from lbryio/filter-selectors
fix filter selectors getting stuck
2019-08-26 22:17:18 +01:00
Akinwale Ariwodola
9391c1b78c fix filter selectors getting stuck 2019-08-26 22:13:58 +01:00
Akinwale Ariwodola
e902420269 fix: pin react-native-gesture-handler package 2019-08-26 21:17:38 +01:00
Akinwale Ariwodola
dc0d3ad9b6 fix: not_tags and npm packages 2019-08-26 21:01:37 +01:00
Akinwale Ariwodola
1c70fc878a fix: buildURI calls 2019-08-26 20:39:51 +01:00
Akinwale Ariwodola
47cdf2698f tweak transaction history page 2019-08-25 17:41:37 +01:00
Akinwale Ariwodola
cf5045cce3 tweak transaction list item 2019-08-25 17:34:26 +01:00
Akinwale Ariwodola
e6139d59d9
edit published content ()
* edit published content
* fix: error from clicking post-publish link
* fixes from ui/ux review
2019-08-25 17:08:43 +01:00
Akinwale Ariwodola
a45e9c5513
Merge pull request from lbryio/fix-send-tip
send tip fix and tweaks
2019-08-25 16:37:40 +01:00
Akinwale Ariwodola
68d99ac639 setCurrentScreen tweaks 2019-08-25 16:35:51 +01:00
Akinwale Ariwodola
10e92b2834 send tip fix and tweaks 2019-08-23 09:19:02 +01:00
Akinwale Ariwodola
384550cc63 tweak reward icon size on file page 2019-08-22 17:06:29 +01:00
Akinwale Ariwodola
3f7408907a change reward icon colour 2019-08-22 17:03:30 +01:00
Akinwale Ariwodola
4ff2fb1def show reward icon in content lists and file page 2019-08-22 17:00:06 +01:00
Akinwale Ariwodola
1e31d8d22b fix focused for your tags and wallet menu items 2019-08-21 18:05:22 +01:00
Akinwale Ariwodola
430fc1a661
Merge pull request from lbryio/tweaks
tweaks to publish gallery and featured search result
2019-08-21 16:51:09 +01:00
Akinwale Ariwodola
7dd4f96e65 tweaks to publish gallery and featured search result 2019-08-21 16:49:53 +01:00
Akinwale Ariwodola
1e6505b664 tweak video loading indicator 2019-08-21 07:05:43 +01:00
Akinwale Ariwodola
3ed97ff3da tweak publish page grid 2019-08-21 04:53:35 +01:00
Akinwale Ariwodola
3cb5d34511 fix: your tags and wallet route navigation 2019-08-20 18:42:44 +01:00
Akinwale Ariwodola
8d5ce92f81
Merge pull request from lbryio/search-refactor
refactor search results page for improved performance
2019-08-20 16:33:04 +01:00
Akinwale Ariwodola
4c864c00dd refactor search results page for improved performance 2019-08-20 10:47:39 +01:00
Akinwale Ariwodola
bbdea90e0f
Merge pull request from lbryio/fix-channel-creation
auto-select channel upon creation
2019-08-20 10:14:14 +01:00
Akinwale Ariwodola
719604e7da auto-select channel upon creation 2019-08-20 10:11:35 +01:00
Akinwale Ariwodola
6ae8c32f72 fix long press on file list items 2019-08-20 09:25:11 +01:00
Akinwale Ariwodola
4287793d3f
0.8.2 rc ()
* enable deletion of downloads on the Library page
* tweak suggested subscriptions and library page
* pin lbryinc commit hash
2019-08-20 09:19:42 +01:00
Akinwale Ariwodola
a2ee73e23e
display the video duration in the FileItemMedia component () 2019-08-20 09:03:55 +01:00
Akinwale Ariwodola
6653225b61
rework discovery views with new/top/trending sorting ()
* rework discovery views with new/top/trending sorting
* use claimList on channel page
* use first letter placeholder for channels without thumbnails
* list header style tweak
* drawer menu redesign
* do not hide channel on publishes view
* add setCurrentScreen calls
* fix: merge conflict
2019-08-20 09:03:33 +01:00
Akinwale Ariwodola
41f511e233
list selection mode and deleting published items () 2019-08-16 18:26:13 +01:00
Akinwale Ariwodola
2c6ac47d1a formatCredits with shortFormat 2019-08-16 09:41:02 +01:00
Akinwale Ariwodola
1bc2b47145
improve gallery thumbnail loading and display ()
* progressively display gallery thumbnails. publish style tweaks.
2019-08-15 19:28:47 +01:00
Akinwale Ariwodola
5c80e7f1cf
Floating balance icon ()
* use coins icon for floating balance
2019-08-15 19:19:01 +01:00
Akinwale Ariwodola
0d06ce5844
Merge pull request from lbryio/revert-23-coins
Revert "Floating balance icon"
2019-08-15 19:17:32 +01:00
Akinwale Ariwodola
33d525f5c7 Revert "Floating balance icon ()"
This reverts commit 84fe0f69d5.
2019-08-15 19:17:17 +01:00
Akinwale Ariwodola
84fe0f69d5
Floating balance icon ()
* use coins icon for floating balance
2019-08-15 19:15:41 +01:00
Akinwale Ariwodola
e443a0fdbb limit recent transactions to 5 items 2019-08-15 13:02:14 +01:00
Akinwale Ariwodola
580f879d0e fix references to FULL_ROUTE_NAME_TRENDING 2019-08-15 10:09:26 +01:00
Akinwale Ariwodola
8fc179cbde fix big seeker circle style 2019-08-15 10:03:12 +01:00
Akinwale Ariwodola
df05825763 tweak media player seeker top position in fullscreen mode 2019-08-15 09:58:43 +01:00
Akinwale Ariwodola
acf0095be9
Merge pull request from lbryio/blank-password-warning
change blank password warning text style
2019-08-15 05:32:41 +01:00
Akinwale Ariwodola
1e72672b4b change blank password warning text style 2019-08-15 05:31:16 +01:00
Akinwale Ariwodola
319cf3abad
Merge pull request from lbryio/publish-url-bid
display content address card in simple mode
2019-08-15 05:14:44 +01:00
Akinwale Ariwodola
73ad23ef58 display content address card in simple mode 2019-08-15 05:11:31 +01:00
Akinwale Ariwodola
0041059465
Redux master updates ()
* update to work with new claimSearch api
* pin new lbry-redux commit
* gfycat style names prefilled for all publishes
* do not show the file rewards driver if we've already got the file
* set state correctly for generated names
* i18n per review
2019-08-15 04:55:01 +01:00
Akinwale Ariwodola
47378d9191
Merge pull request from lbryio/drawer-stutter
fix drawer menu stuttering and getting stuck
2019-08-13 09:34:47 +01:00
Akinwale Ariwodola
2e7660250b fix drawer menu stuttering and getting stuck 2019-08-13 09:07:44 +01:00
Akinwale Ariwodola
8b67a9fab8
Merge pull request from lbryio/uri-bar-spellcheck
disable spell check in uri bar
2019-08-13 04:55:41 +01:00
Akinwale Ariwodola
95285cf4dd disable spell check in uri bar 2019-08-13 01:59:48 +01:00
Akinwale Ariwodola
60c617f334
Merge pull request from lbryio/final-fixes
fix remaining publish bugs
2019-08-12 13:20:17 +01:00
Akinwale Ariwodola
1853558215 fix remaining publish bugs 2019-08-12 13:18:47 +01:00
Akinwale Ariwodola
f05181898b fix navigate back to trending and claim_search end detection 2019-08-11 18:36:47 +01:00
Akinwale Ariwodola
4bd26d4e2e
Merge pull request from lbryio/yet-more-fixes
fix related content. fix empty vertical claim list.
2019-08-11 17:15:01 +01:00
Akinwale Ariwodola
c57449abf7 fix related content. fix empty vertical claim list. 2019-08-11 12:03:41 +01:00
Akinwale Ariwodola
38618080a1
Misc tweaks and fixes ()
* channel icon auto thumbs. fix infinite claim list reload.
* tweak last page check
* additional tweaks and fixes before release
2019-08-11 08:52:17 +01:00
Akinwale Ariwodola
89222bc9ee
Merge pull request from lbryio/publish-fixes
fix publishing bugs
2019-08-11 00:07:25 +01:00
Akinwale Ariwodola
fc412e4282 fix publishing bugs 2019-08-11 00:06:10 +01:00
Akinwale Ariwodola
ffccc124d0
Merge pull request from lbryio/more-fixes
display remaining discover tags and fix publish cancel bug
2019-08-10 10:13:28 +01:00
Akinwale Ariwodola
e8704b1c92 display remaining discover tags and fix publish cancel bug 2019-08-10 10:12:43 +01:00
Akinwale Ariwodola
86b4a825c8 don't show anonymous while resolving file item 2019-08-10 08:12:27 +01:00
Akinwale Ariwodola
95b6801450 Daemon (lbrynet) --> LBRY SDK 2019-08-10 08:01:25 +01:00
Akinwale Ariwodola
1e0a636b4f
Fix release blockers () 2019-08-10 07:55:12 +01:00
Akinwale Ariwodola
4b7f3f140a add unmountInactiveRoutes to drawer navigator 2019-08-09 07:52:48 +01:00
Akinwale Ariwodola
18d654cc66
Performance ()
* performance improvements
* fix time picker for top content on tag page
* redux-persist v5. style tweaks.
2019-08-09 07:41:40 +01:00
Thomas Zarebczan
22d860a885 remove uri param to doSendTip ()
* remove uri param to doSendTip

This was not being used in redux and I removed it recently.

* fix doSendtip

We added an isSupport parameter on redux side to support supports :)

* pass isSupport false for tips
2019-07-30 16:15:50 +01:00
219 changed files with 22705 additions and 12825 deletions
.eslintrc.json.gitignore.gitlab-ci.yml.gitmodulesLICENSEREADME.md
__tests__
androidbundle-android.shindex.js
ios
LbryApp-tvOS
LbryApp-tvOSTests
LbryApp.xcodeproj
LbryApp
LbryAppTests
Podfile
lbry-app-strings.jsonpackage-lock.jsonpackage.jsonreactotron.js
src
assets
component
AppNavigator.js
address
button
categoryList
channelIconItem
channelRewardsDriver
channelSelector
claimList
claimResultItem
customRewardCard
drawerContent
emptyStateView
fileDownloadButton
fileItem
fileItemMedia
fileList
fileListItem
filePrice
fileRewardsDriver
floatingWalletBalance
mediaPlayer
modalPicker
modalRepostView
modalSuggestedSubscriptions
modalTagSelector
modalTipView
nsfwOverlay
publishRewardsDriver
relatedContent
rewardCard
rewardEnrolment
rewardSummary
sdkLoadingStatus
searchInput
storageStatsCard
subscribeButton
subscribeButtonOverlay
subscribeNotificationButton
suggestedSubscriptionItem
suggestedSubscriptions
suggestedSubscriptionsGrid
tag
tagSearch
transactionList
transactionListRecent
uriBar

View file

@ -10,6 +10,7 @@
"__": true
},
"rules": {
"no-console": 2,
"no-multi-spaces": 0,
"new-cap": 0,
"prefer-promise-reject-errors": 0,

64
.gitignore vendored
View file

@ -1,4 +1,66 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
*.keystore
!debug.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle
# CocoaPods
/ios/Pods/
# Other Files
android/app/google-services.json
*.log
.vagrant
android/app/build/*
android/bin

8
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,8 @@
stages:
- build
build:
variables:
REACT_NATIVE_BRANCH: $CI_COMMIT_REF_NAME
stage: build
trigger: lbry/lbry-android

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "android"]
path = android
url = https://github.com/lbryio/lbry-android

View file

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2017-2018 LBRY Inc
Copyright (c) 2017-2020 LBRY Inc
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View file

@ -1,7 +1,7 @@
# LBRY Android
# LBRY React Native
[![pipeline status](https://ci.lbry.tech/lbry/lbry-android/badges/master/pipeline.svg)](https://ci.lbry.tech/lbry/lbry-android/commits/master)
An Android browser and wallet for the [LBRY](https://lbry.com) network. This app bundles [lbrynet-daemon](https://github.com/lbryio/lbry) as a background service with a UI layer built with React Native. The APK is built using buildozer and the Gradle build tool.
A mobile browser and wallet for the [LBRY](https://lbry.com) network. This app bundles [LBRY SDK](https://github.com/lbryio/lbry) as a background service with a UI layer built with React Native.
<img src="https://spee.ch/8/lbry-android.png" alt="LBRY Android Screenshot" width="384px" />
@ -12,10 +12,41 @@ The minimum supported Android version is 5.0 Lollipop. There are two ways to ins
1. Direct APK install available at [http://build.lbry.io/android/latest.apk](http://build.lbry.io/android/latest.apk). You will need to enable installation from third-party sources on your device in order to install from this source.
## Usage
The app can be launched by opening **LBRY Browser** from the device's app drawer or via the shortcut on the home screen if that was created upon installation.
The app can be launched by opening **LBRY** from the device's app drawer or via the shortcut on the home screen if that was created upon installation.
## Running from Source
The app is built from source via [Buildozer](https://github.com/kivy/buildozer). After cloning the repository, copy `buildozer.spec.sample` to `buildozer.spec` and modify this file as necessary for your environment. Please see [BUILD.md](BUILD.md) for detailed build instructions.
### Software Requirements
* Android Studio
* WebStorm (or other IDE for editing React Native / JavaScript code)
* npm
* yarn
### Android Steps
* Clone the repository using `git clone https://github.com/lbryio/lbry-react-native`
* Initialise the submodules.
```
cd lbry-react-native
git submodule update --init --recursive
```
* Install `react-native-cli` globally using `npm install -g react-native-cli`.
* Install the required package modules by running `yarn` in the cloned repository folder.
* Download a `google-services.json` from the Firebase console (https://console.firebase.google.com/) and place it in the `android/app` folder. Alternatively, use the included sample JSON file.
```
cp android/app/google-services.sample.json android/app/google-services.json
```
* Open Android Studio and click File > Open...
* Navigate to the cloned repository on your local filesystem and select the `android` subfolder.
* Connect your Android device in USB debugging mode, or create an ARM emulator (slower) to run the app.
* Click Run > Run... and select the corresponding app configuration. Note that it may take a while for the project files to sync before you can run the app
* In order to edit the React Native / JavaScript files, open the cloned repository folder using WebStorm (or your favourite IDE).
### React Native Fast Refresh
In order to enable fast refresh when updating React Native code
* Connect your Android device in USB debugging mode, or create an ARM emulator
* Run `adb reverse tcp:8081 tcp:8081` (`adb` can be found in the `platform-tools` folder of your Android SDK installation)
* Run `yarn start`
* Press `r` to reload the app.
* Anytime you make an update to the React Native code, the app should automatically refresh.
## Contributing
Contributions to this project are welcome, encouraged, and compensated. For more details, see https://lbry.io/faq/contributing

14
__tests__/App-test.js Normal file
View file

@ -0,0 +1,14 @@
/**
* @format
*/
import 'react-native';
import React from 'react';
import App from '../App';
// Note: test renderer must be required after react-native.
import renderer from 'react-test-renderer';
it('renders correctly', () => {
renderer.create(<App />);
});

1
android Submodule

@ -0,0 +1 @@
Subproject commit ff30e7f6a4358fd997a9e6d9f75bfe6959eafcb6

2
bundle-android.sh Normal file
View file

@ -0,0 +1,2 @@
#!/bin/bash
react-native bundle --platform android --dev false --entry-file src/index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

View file

@ -1,3 +1,7 @@
if (__DEV__) {
import('./reactotron').then(() => console.log('Reactotron Configured'));
}
import LBRYApp from './src/index';
export default LBRYApp;

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -0,0 +1,782 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* LbryAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* LbryAppTests.m */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DCD954D1E0B4F2C00145EB5 /* LbryAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* LbryAppTests.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 13B07F861A680F5B00A75B9A;
remoteInfo = LbryApp;
};
2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 2D02E47A1E0B4A5D006451C7;
remoteInfo = "LbryApp-tvOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = "<group>"; };
00E356EE1AD99517003FC87E /* LbryAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = LbryAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* LbryAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LbryAppTests.m; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* LbryApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LbryApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = LbryApp/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = LbryApp/AppDelegate.m; sourceTree = "<group>"; };
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = LbryApp/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = LbryApp/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = LbryApp/main.m; sourceTree = "<group>"; };
2D02E47B1E0B4A5D006451C7 /* LbryApp-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "LbryApp-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2D02E4901E0B4A5D006451C7 /* LbryApp-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LbryApp-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
00E356EB1AD99517003FC87E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E4781E0B4A5D006451C7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E48D1E0B4A5D006451C7 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
00E356EF1AD99517003FC87E /* LbryAppTests */ = {
isa = PBXGroup;
children = (
00E356F21AD99517003FC87E /* LbryAppTests.m */,
00E356F01AD99517003FC87E /* Supporting Files */,
);
path = LbryAppTests;
sourceTree = "<group>";
};
00E356F01AD99517003FC87E /* Supporting Files */ = {
isa = PBXGroup;
children = (
00E356F11AD99517003FC87E /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
13B07FAE1A68108700A75B9A /* LbryApp */ = {
isa = PBXGroup;
children = (
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB11A68108700A75B9A /* LaunchScreen.xib */,
13B07FB71A68108700A75B9A /* main.m */,
);
name = LbryApp;
sourceTree = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
ED2971642150620600B7C4FE /* JavaScriptCore.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* LbryApp */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* LbryAppTests */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* LbryApp.app */,
00E356EE1AD99517003FC87E /* LbryAppTests.xctest */,
2D02E47B1E0B4A5D006451C7 /* LbryApp-tvOS.app */,
2D02E4901E0B4A5D006451C7 /* LbryApp-tvOSTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
00E356ED1AD99517003FC87E /* LbryAppTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "LbryAppTests" */;
buildPhases = (
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
);
buildRules = (
);
dependencies = (
00E356F51AD99517003FC87E /* PBXTargetDependency */,
);
name = LbryAppTests;
productName = LbryAppTests;
productReference = 00E356EE1AD99517003FC87E /* LbryAppTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
13B07F861A680F5B00A75B9A /* LbryApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "LbryApp" */;
buildPhases = (
FD10A7F022414F080027D42C /* Start Packager */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
);
buildRules = (
);
dependencies = (
);
name = LbryApp;
productName = "LbryApp";
productReference = 13B07F961A680F5B00A75B9A /* LbryApp.app */;
productType = "com.apple.product-type.application";
};
2D02E47A1E0B4A5D006451C7 /* LbryApp-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "LbryApp-tvOS" */;
buildPhases = (
FD10A7F122414F3F0027D42C /* Start Packager */,
2D02E4771E0B4A5D006451C7 /* Sources */,
2D02E4781E0B4A5D006451C7 /* Frameworks */,
2D02E4791E0B4A5D006451C7 /* Resources */,
2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */,
);
buildRules = (
);
dependencies = (
);
name = "LbryApp-tvOS";
productName = "LbryApp-tvOS";
productReference = 2D02E47B1E0B4A5D006451C7 /* LbryApp-tvOS.app */;
productType = "com.apple.product-type.application";
};
2D02E48F1E0B4A5D006451C7 /* LbryApp-tvOSTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "LbryApp-tvOSTests" */;
buildPhases = (
2D02E48C1E0B4A5D006451C7 /* Sources */,
2D02E48D1E0B4A5D006451C7 /* Frameworks */,
2D02E48E1E0B4A5D006451C7 /* Resources */,
);
buildRules = (
);
dependencies = (
2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */,
);
name = "LbryApp-tvOSTests";
productName = "LbryApp-tvOSTests";
productReference = 2D02E4901E0B4A5D006451C7 /* LbryApp-tvOSTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0940;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
CreatedOnToolsVersion = 6.2;
TestTargetID = 13B07F861A680F5B00A75B9A;
};
2D02E47A1E0B4A5D006451C7 = {
CreatedOnToolsVersion = 8.2.1;
ProvisioningStyle = Automatic;
};
2D02E48F1E0B4A5D006451C7 = {
CreatedOnToolsVersion = 8.2.1;
ProvisioningStyle = Automatic;
TestTargetID = 2D02E47A1E0B4A5D006451C7;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "LbryApp" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* LbryApp */,
00E356ED1AD99517003FC87E /* LbryAppTests */,
2D02E47A1E0B4A5D006451C7 /* LbryApp-tvOS */,
2D02E48F1E0B4A5D006451C7 /* LbryApp-tvOSTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
00E356EC1AD99517003FC87E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F8E1A680F5B00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E4791E0B4A5D006451C7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E48E1E0B4A5D006451C7 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
2D02E4CB1E0B4B27006451C7 /* Bundle React Native Code And Images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native Code And Images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh";
};
FD10A7F022414F080027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
FD10A7F122414F3F0027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Start Packager";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
00E356EA1AD99517003FC87E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
00E356F31AD99517003FC87E /* LbryAppTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
13B07F871A680F5B00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E4771E0B4A5D006451C7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */,
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
2D02E48C1E0B4A5D006451C7 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2DCD954D1E0B4F2C00145EB5 /* LbryAppTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 13B07F861A680F5B00A75B9A /* LbryApp */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
2D02E4921E0B4A5D006451C7 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 2D02E47A1E0B4A5D006451C7 /* LbryApp-tvOS */;
targetProxy = 2D02E4911E0B4A5D006451C7 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = {
isa = PBXVariantGroup;
children = (
13B07FB21A68108700A75B9A /* Base */,
);
name = LaunchScreen.xib;
path = LbryApp;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = LbryAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LbryApp.app/LbryApp";
};
name = Debug;
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
INFOPLIST_FILE = LbryAppTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"-ObjC",
"-lc++",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LbryApp.app/LbryApp";
};
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = NO;
INFOPLIST_FILE = LbryApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = LbryApp;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
INFOPLIST_FILE = LbryApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = LbryApp;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
2D02E4971E0B4A5E006451C7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "LbryApp-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.LbryApp-tvOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.2;
};
name = Debug;
};
2D02E4981E0B4A5E006451C7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "LbryApp-tvOS/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.LbryApp-tvOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 9.2;
};
name = Release;
};
2D02E4991E0B4A5E006451C7 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "LbryApp-tvOSTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.LbryApp-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LbryApp-tvOS.app/LbryApp-tvOS";
TVOS_DEPLOYMENT_TARGET = 10.1;
};
name = Debug;
};
2D02E49A1E0B4A5E006451C7 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "LbryApp-tvOSTests/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.LbryApp-tvOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/LbryApp-tvOS.app/LbryApp-tvOS";
TVOS_DEPLOYMENT_TARGET = 10.1;
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "LbryAppTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
00E356F61AD99517003FC87E /* Debug */,
00E356F71AD99517003FC87E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "LbryApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
13B07F951A680F5B00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2D02E4BA1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "LbryApp-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D02E4971E0B4A5E006451C7 /* Debug */,
2D02E4981E0B4A5E006451C7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2D02E4BB1E0B4A5E006451C7 /* Build configuration list for PBXNativeTarget "LbryApp-tvOSTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2D02E4991E0B4A5E006451C7 /* Debug */,
2D02E49A1E0B4A5E006451C7 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "LbryApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}

View file

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D2A28121D9B038B00D4039D"
BuildableName = "libReact.a"
BlueprintName = "React-tvOS"
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "LbryApp-tvOS.app"
BlueprintName = "LbryApp-tvOS"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
BuildableName = "LbryApp-tvOSTests.xctest"
BlueprintName = "LbryApp-tvOSTests"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
BuildableName = "LbryApp-tvOSTests.xctest"
BlueprintName = "LbryApp-tvOSTests"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "LbryApp-tvOS.app"
BlueprintName = "LbryApp-tvOS"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "LbryApp-tvOS.app"
BlueprintName = "LbryApp-tvOS"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
BuildableName = "LbryApp-tvOS.app"
BlueprintName = "LbryApp-tvOS"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0940"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
BuildableName = "libReact.a"
BlueprintName = "React"
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "LbryApp.app"
BlueprintName = "LbryApp"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "LbryAppTests.xctest"
BlueprintName = "LbryAppTests"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
BuildableName = "LbryAppTests.xctest"
BlueprintName = "LbryAppTests"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "LbryApp.app"
BlueprintName = "LbryApp"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "LbryApp.app"
BlueprintName = "LbryApp"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "LbryApp.app"
BlueprintName = "LbryApp"
ReferencedContainer = "container:LbryApp.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

15
ios/LbryApp/AppDelegate.h Normal file
View file

@ -0,0 +1,15 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
@property (nonatomic, strong) UIWindow *window;
@end

42
ios/LbryApp/AppDelegate.m Normal file
View file

@ -0,0 +1,42 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"LbryApp"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
@end

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="LbryApp" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View file

@ -0,0 +1,38 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

57
ios/LbryApp/Info.plist Normal file
View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>LbryApp</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

16
ios/LbryApp/main.m Normal file
View file

@ -0,0 +1,16 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View file

@ -0,0 +1,72 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#define TIMEOUT_SECONDS 600
#define TEXT_TO_LOOK_FOR @"Welcome to React"
@interface LbryAppTests : XCTestCase
@end
@implementation LbryAppTests
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen
{
UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
__block NSString *redboxError = nil;
#ifdef DEBUG
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
redboxError = message;
}
});
#endif
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];
}
#ifdef DEBUG
RCTSetLogFunction(RCTDefaultLogFunction);
#endif
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
@end

53
ios/Podfile Normal file
View file

@ -0,0 +1,53 @@
platform :ios, '9.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
target 'LbryApp' do
# Pods for LbryAndroid
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/'
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
target 'LbryAndroidTests' do
inherit! :search_paths
# Pods for testing
end
use_native_modules!
end
target 'LbryAndroid-tvOS' do
# Pods for LbryAndroid-tvOS
target 'LbryAndroid-tvOSTests' do
inherit! :search_paths
# Pods for testing
end
end

326
lbry-app-strings.json Normal file
View file

@ -0,0 +1,326 @@
{
"Please wait while we get some things ready...": "Please wait while we get some things ready...",
"Welcome to LBRY.": "Welcome to LBRY.",
"LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.": "LBRY is a community-controlled content platform where you can find and publish videos, music, books, and more.",
"Continue": "Continue",
"Setup account": "Setup account",
"A lbry.tv account allows you to earn rewards, backup your wallet, and keep everything in sync.": "A lbry.tv account allows you to earn rewards, backup your wallet, and keep everything in sync.",
"This information is disclosed only to LBRY, Inc. and not to the LBRY network.": "This information is disclosed only to LBRY, Inc. and not to the LBRY network.",
"No, thanks": "No, thanks",
"Use LBRY": "Use LBRY",
"Are you sure?": "Are you sure?",
"Without an account, you will not receive rewards, sync and backup services, or security updates.": "Without an account, you will not receive rewards, sync and backup services, or security updates.",
"I understand that by uninstalling LBRY I will lose any balances or published content with no recovery option if it is not backed up manually (see wallet page)": "I understand that by uninstalling LBRY I will lose any balances or published content with no recovery option if it is not backed up manually (see wallet page)",
"Starting up": "Starting up",
"Connecting": "Connecting",
"Testing network": "Testing network",
"Waiting for name resolution": "Waiting for name resolution",
"Search movies, music, and more": "Search movies, music, and more",
"Your Tags": "Your Tags",
"Trending": "Trending",
"Customize": "Customize",
"Sign In": "Sign In",
"SIGN IN": "SIGN IN",
"Find content": "Find content",
"Subscriptions": "Subscriptions",
"All Content": "All Content",
"Your content": "Your content",
"Channels": "Channels",
"Library": "Library",
"Publishes": "Publishes",
"New Publish": "New Publish",
"Wallet": "Wallet",
"Rewards": "Rewards",
"Settings": "Settings",
"About": "About",
"All tags you follow": "All tags you follow",
"More tags you follow": "More tags you follow",
"FREE": "FREE",
"more": "more",
"You have not created a channel.\nStart now by creating a new channel!": "You have not created a channel.\nStart now by creating a new channel!",
"Create a channel": "Create a channel",
"CONTENT": "CONTENT",
"ABOUT": "ABOUT",
"Follow": "Follow",
"No content to display at this time. Please check back later.": "No content to display at this time. Please check back later.",
"New": "New",
"Share": "Share",
"Tip": "Tip",
"Report": "Report",
"Fetching cost info...": "Fetching cost info...",
"Play": "Play",
"Connecting...": "Connecting...",
"Related Content": "Related Content",
"Loading...": "Loading...",
"Content Freedom": "Content Freedom",
"LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer content distribution platform for creators to upload and share content, and earn LBRY credits for their effort. Users will be able to find a wide selection of videos, music, ebooks and other digital content they are interested in.": "LBRY is a free, open, and community-run digital marketplace. It is a decentralized peer-to-peer content distribution platform for creators to upload and share content, and earn LBRY credits for their effort. Users will be able to find a wide selection of videos, music, ebooks and other digital content they are interested in.",
"What is LBRY?": "What is LBRY?",
"Android App Basics": "Android App Basics",
"Frequently Asked Questions": "Frequently Asked Questions",
"Get Social": "Get Social",
"You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or Reddit.": "You can interact with the LBRY team and members of the community on Discord, Facebook, Instagram, Twitter or Reddit.",
"App info": "App info",
"App version": "App version",
"LBRY SDK": "LBRY SDK",
"Platform": "Platform",
"Installation ID": "Installation ID",
"Logs": "Logs",
"Send log": "Send log",
"Account Recommended": "Account Recommended",
"Without an account, you assume all responsibility for securing your wallet and LBRY data.": "Without an account, you assume all responsibility for securing your wallet and LBRY data.",
"Skip Account": "Skip Account",
"Sign Up": "Sign Up",
"Blockchain Sync": "Blockchain Sync",
"Catching up with the blockchain (%progress%%)": "Catching up with the blockchain (%progress%%)",
"Network Loading": "Network Loading",
"Initializing LBRY service": "Initializing LBRY service",
"%amount% available credits": "%amount% available credits",
"LBRY credits allow you to purchase content, publish content, and influence the network.": "LBRY credits allow you to purchase content, publish content, and influence the network.",
"You get credits for free for providing an email address and taking other basic actions.": "You get credits for free for providing an email address and taking other basic actions.",
"Learn more": "Learn more",
"Not interested": "Not interested",
"Get started": "Get started",
"Get %amount% free credits after creating an account.": "Get %amount% free credits after creating an account.",
"Balance": "Balance",
"You currently have": "You currently have",
"Receive Credits": "Receive Credits",
"Use this wallet address to receive credits sent by another user (or yourself).": "Use this wallet address to receive credits sent by another user (or yourself).",
"Get new address": "Get new address",
"You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.": "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.",
"Send Credits": "Send Credits",
"Recipient address": "Recipient address",
"Amount": "Amount",
"Send": "Send",
"Recent Transactions": "Recent Transactions",
"View All": "View All",
"Looks like you don't have any recent transactions.": "Looks like you don't have any recent transactions.",
"Wallet Sync": "Wallet Sync",
"Sync status": "Sync status",
"Off": "Off",
"Manual backup": "Manual backup",
"Sync FAQ": "Sync FAQ",
"Fetching transactions...": "Fetching transactions...",
"Anonymous": "Anonymous",
"Delete": "Delete",
"Title": "Title",
"Channel": "Channel",
"Deposit": "Deposit",
"This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.": "This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.",
"Show optional fields": "Show optional fields",
"Cancel": "Cancel",
"Create": "Create",
"Channel creation requires credits.": "Channel creation requires credits.",
"Tap here to get some for free.": "Tap here to get some for free.",
"%balance% available": "%balance% available",
"Bal: %balance%": "Bal: %balance%",
"Send a tip": "Send a tip",
"Record": "Record",
"Take a photo": "Take a photo",
"Upload a file": "Upload a file",
"Please wait while we load your videos...": "Please wait while we load your videos...",
"Description": "Description",
"Tags": "Tags",
"Price": "Price",
"Your content will be free. Press the toggle to set a price.": "Your content will be free. Press the toggle to set a price.",
"Content address": "Content address",
"The address where people can find your content (ex. lbry://myvideo). ": "The address where people can find your content (ex. lbry://myvideo). ",
"This LBC remains yours and the deposit can be undone at any time.": "This LBC remains yours and the deposit can be undone at any time.",
"Show extra fields": "Show extra fields",
"Publish": "Publish",
"Publishing requires credits.": "Publishing requires credits.",
"Search for more tags": "Search for more tags",
"Mature tags": "Mature tags",
"Publish anonymously": "Publish anonymously",
"Create a channel...": "Create a channel...",
"Uploading thumbnail...": "Uploading thumbnail...",
"Channel name": "Channel name",
"Deposit cannot be higher than your balance": "Deposit cannot be higher than your balance",
"There are no results to display for \"%query%\". Please try a different search term.": "There are no results to display for \"%query%\". Please try a different search term.",
"Nothing here. Publish something!": "Nothing here. Publish something!",
"Explore content for this tag": "Explore content for this tag",
"URL does not include name.": "URL does not include name.",
"Loading decentralized data...": "Loading decentralized data...",
"View": "View",
"Additional Options": "Additional Options",
"Language": "Language",
"English": "English",
"Chinese": "Chinese",
"French": "French",
"German": "German",
"Japanese": "Japanese",
"Russian": "Russian",
"Spanish": "Spanish",
"Indonesian": "Indonesian",
"Italian": "Italian",
"Dutch": "Dutch",
"Turkish": "Turkish",
"Polish": "Polish",
"Malay": "Malay",
"Portuguese": "Portuguese",
"Vietnamese": "Vietnamese",
"Thai": "Thai",
"Arabic": "Arabic",
"Czech": "Czech",
"Croatian": "Croatian",
"Cambodian": "Cambodian",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Romanian": "Romanian",
"Hindi": "Hindi",
"Greek": "Greek",
"License": "License",
"None": "None",
"Public Domain": "Public Domain",
"Copyrighted...": "Copyrighted...",
"Other...": "Other...",
"Hide extra fields": "Hide extra fields",
"Download": "Download",
"%progress%% complete": "%progress%% complete",
"Email": "Email",
"Please provide an email address.": "Please provide an email address.",
"An email has been sent to": "An email has been sent to",
"Please click the link in the message to complete signing in.": "Please click the link in the message to complete signing in.",
"Resend": "Resend",
"Edit": "Edit",
"Your email address was successfully verified.": "Your email address was successfully verified.",
"password": "password",
"Please enter a password to secure your account and wallet.": "Please enter a password to secure your account and wallet.",
"Retrieving your account information...": "Retrieving your account information...",
"Please enter the password you used to secure your wallet.": "Please enter the password you used to secure your wallet.",
"Note: for wallet security purposes, LBRY is unable to reset your password.": "Note: for wallet security purposes, LBRY is unable to reset your password.",
"On": "On",
"Connected email": "Connected email",
"Address copied": "Address copied",
"Unlocking account": "Unlocking account",
"Decrypting wallet": "Decrypting wallet",
"used": "used",
"Stats": "Stats",
"Channels you follow": "Channels you follow",
"Suggested": "Suggested",
"ALL": "ALL",
"Customize your tags": "Customize your tags",
"Done": "Done",
"Unfollow": "Unfollow",
"This content is Not Safe For Work. To view adult content, please change your Settings.": "This content is Not Safe For Work. To view adult content, please change your Settings.",
"No views": "No views",
"%view% views": "%view% views",
"Content": "Content",
"Enable background media playback": "Enable background media playback",
"Enable this option to play audio or video in the background when the app is suspended.": "Enable this option to play audio or video in the background when the app is suspended.",
"Choose language": "Choose language",
"Use device language": "Use device language",
"Gujarati": "Gujarati",
"Show mature content": "Show mature content",
"Notifications": "Notifications",
"Choose the notifications you would like to receive.": "Choose the notifications you would like to receive.",
"Content Interests": "Content Interests",
"Search": "Search",
"Show URL suggestions": "Show URL suggestions",
"Other": "Other",
"Keep the SDK background service running after closing the app": "Keep the SDK background service running after closing the app",
"Enable this option for quicker app launch and to keep the synchronisation with the blockchain up to date.": "Enable this option for quicker app launch and to keep the synchronisation with the blockchain up to date.",
"There's nothing here yet.\nPlease check back later.": "There's nothing here yet.\nPlease check back later.",
"%follower% followers": "%follower% followers",
"Are you sure you want to tip %amount% credits": "Are you sure you want to tip %amount% credits",
"Send tip": "Send tip",
"No": "No",
"Yes": "Yes",
"History": "History",
"/wallet": "/wallet",
"Are you sure you want to tip %amount% credit": "Are you sure you want to tip %amount% credit",
"Enjoying LBRY?": "Enjoying LBRY?",
"Are you enjoying your experience with the LBRY app? You can leave a review for us on the Play Store.": "Are you enjoying your experience with the LBRY app? You can leave a review for us on the Play Store.",
"Never ask again": "Never ask again",
"Maybe later": "Maybe later",
"Rate app": "Rate app",
"This will appear as a tip for %content%, which will boost its ability to be discovered while active.": "This will appear as a tip for %content%, which will boost its ability to be discovered while active.",
"Learn more.": "Learn more.",
"Send a tip to %channel%": "Send a tip to %channel%",
"The transaction URL could not be opened": "The transaction URL could not be opened",
"Show All": "Show All",
"Fetching rewards...": "Fetching rewards...",
"Custom Code": "Custom Code",
"Are you a supermodel or rockstar that received a custom reward code? Claim it here.": "Are you a supermodel or rockstar that received a custom reward code? Claim it here.",
"Redeem": "Redeem",
"Please confirm that you want to use LBRY without creating an account.": "Please confirm that you want to use LBRY without creating an account.",
"You sent %amount% LBC as a tip, Mahalo!": "You sent %amount% LBC as a tip, Mahalo!",
"%follower% follower": "%follower% follower",
"Authenticating": "Authenticating",
"Waiting for authentication": "Waiting for authentication",
"Delete file": "Delete file",
"Are you sure you want to remove this file from your device?": "Are you sure you want to remove this file from your device?",
"Following": "Following",
"Update mailing preferences": "Update mailing preferences",
"Please confirm you want to tip %amount% credits": "Please confirm you want to tip %amount% credits",
"Send Tip": "Send Tip",
"You do not have any\ndownloaded content on this device.": "You do not have any\ndownloaded content on this device.",
"%view% view": "%view% view",
"No channel name after @.": "No channel name after @.",
"Loading transactions...": "Loading transactions...",
"Pending": "Pending",
"Phone Number": "Phone Number",
"Please provide a phone number to prevent fraud.": "Please provide a phone number to prevent fraud.",
"Send verification text": "Send verification text",
"Open": "Open",
"for": "for",
"Everyone": "Everyone",
"Suggested channels": "Suggested channels",
"You might also like": "You might also like",
"Publish something new": "Publish something new",
"Filter for": "Filter for",
"Tags you follow": "Tags you follow",
"Sort content by": "Sort content by",
"Trending content": "Trending content",
"New content": "New content",
"Top content": "Top content",
"Top": "Top",
"Past week": "Past week",
"It looks like you have not\npublished any content to LBRY yet.": "It looks like you have not\npublished any content to LBRY yet.",
"Stop": "Stop",
"Unsupported Content": "Unsupported Content",
"all tags you follow": "all tags you follow",
"Send LBC": "Send LBC",
"from": "from",
"This file cannot be displayed in the LBRY app.": "This file cannot be displayed in the LBRY app.",
"Please press the Play button.": "Please press the Play button.",
"Stop download": "Stop download",
"Are you sure you want to stop downloading this file?": "Are you sure you want to stop downloading this file?",
"The download will stop momentarily. You do not need to wait to discover something else.": "The download will stop momentarily. You do not need to wait to discover something else.",
"Connection Failure": "Connection Failure",
"We could not establish a connection to the SDK. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.": "We could not establish a connection to the SDK. Your data connection may be preventing LBRY from connecting. Contact hello@lbry.com if you think this is a software bug.",
"%num% block behind": "%num% block behind",
"Delete files": "Delete files",
"Are you sure you want to delete the selected content?": "Are you sure you want to delete the selected content?",
"We could not find any videos on your device. Take a photo or record a video to get started.": "We could not find any videos on your device. Take a photo or record a video to get started.",
"You have already published to the specified content address. Please enter a new address.": "You have already published to the specified content address. Please enter a new address.",
"Camera": "Camera",
"Please grant access to make use of your camera": "Please grant access to make use of your camera",
"OK": "OK",
"Audio": "Audio",
"Please grant access to record audio": "Please grant access to record audio",
"Camera not authorized": "Camera not authorized",
"There's nothing at this location.": "There's nothing at this location.",
"Publish something here": "Publish something here",
"This content cannot be viewed at this time. Please try again in a bit.": "This content cannot be viewed at this time. Please try again in a bit.",
"Download file": "Download file",
"Save %title% (%size%) to your device": "Save %title% (%size%) to your device",
"Save \"%title%\" (%size%) to your device": "Save \"%title%\" (%size%) to your device",
"Find Channels to follow": "Find Channels to follow",
"LBRY works better if you follow at least 5 creators you like. Sign in to show creators you follow if you already have an account.": "LBRY works better if you follow at least 5 creators you like. Sign in to show creators you follow if you already have an account.",
"%remaining% more...": "%remaining% more...",
"Did you know that you can earn free credits worth up to %amount%?": "Did you know that you can earn free credits worth up to %amount%?",
"SHOW ME": "SHOW ME",
"Convert credits to USD on Bittrex": "Convert credits to USD on Bittrex",
"You also have": "You also have",
"in tips": "in tips",
"Earn more tips by uploading cool videos": "Earn more tips by uploading cool videos",
"You staked": "You staked",
"in your publishes": "in your publishes",
"in your supports": "in your supports",
"Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet.": "Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet.",
"A backup of your wallet is synced with lbry.tv": "A backup of your wallet is synced with lbry.tv",
"What does this mean?": "What does this mean?",
"LBRY credits allow you to publish or purchase content.": "LBRY credits allow you to publish or purchase content.",
"You can obtain free credits worth %amount% after you provide an email address.": "You can obtain free credits worth %amount% after you provide an email address.",
"up to": "up to"
}

9301
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,68 +1,90 @@
{
"name": "LBRYApp",
"version": "0.0.1",
"private": "true",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"format": "prettier 'src/**/*.{js,json}' --write",
"precommit": "lint-staged"
},
"dependencies": {
"base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3",
"lbry-redux": "lbryio/lbry-redux#multi-claim-search",
"lbryinc": "lbryio/lbryinc",
"lodash": ">=4.17.11",
"merge": ">=1.2.1",
"moment": "^2.22.1",
"react": "16.8.6",
"react-native": "0.59.10",
"@react-native-community/async-storage": "^1.5.1",
"react-native-camera": "^2.11.0",
"react-native-country-picker-modal": "^0.6.2",
"react-native-document-picker": "^2.3.0",
"react-native-exception-handler": "2.9.0",
"react-native-fast-image": "^5.0.3",
"react-native-fs": "^2.13.3",
"react-native-gesture-handler": "^1.1.0",
"react-native-image-zoom-viewer": "^2.2.5",
"react-native-password-strength-meter": "^0.0.2",
"react-native-phone-input": "lbryio/react-native-phone-input",
"react-native-super-grid": "^3.0.4",
"react-native-vector-icons": "^6.4.2",
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
"react-navigation": "^3.11.0",
"react-navigation-redux-helpers": "^3.0.2",
"react-redux": "^5.0.3",
"redux": "^4.0.4",
"redux-persist": "^5.10.0",
"redux-persist-filesystem-storage": "^1.3.2",
"redux-persist-transform-compress": "^4.2.0",
"redux-persist-transform-filter": "0.0.18",
"redux-thunk": "^2.3.0",
"rn-fetch-blob": "0.10.15"
},
"devDependencies": {
"@babel/core": "^7.5.4",
"babel-eslint": "10.0.2",
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.18.0",
"babel-plugin-module-resolver": "^3.1.1",
"eslint": "^5.16.0",
"eslint-config-standard": "^12.0.0",
"eslint-config-standard-jsx": "^6.0.2",
"eslint-plugin-flowtype": "^2.46.1",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-standard": "^4.0.0",
"flow-babel-webpack-plugin": "^1.1.1",
"husky": "^0.14.3",
"lint-staged": "^7.0.4",
"metro-react-native-babel-preset": "^0.55.0",
"prettier": "^1.11.1"
}
"name": "LBRYApp",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint .",
"format": "prettier 'src/**/*.{js,json}' --write",
"precommit": "lint-staged"
},
"dependencies": {
"base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0",
"gfycat-style-urls": "^1.0.3",
"lbry-redux": "lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017",
"lbryinc": "lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270",
"lodash": ">=4.17.11",
"merge": ">=1.2.1",
"moment": "^2.22.1",
"react": "16.9.0",
"react-native": "0.61.5",
"@react-native-community/async-storage": "^1.5.1",
"@react-native-community/masked-view": "^0.1.5",
"react-native-camera": "^3.15.0",
"react-native-country-picker-modal": "^1.10.0",
"react-native-exception-handler": "2.10.8",
"react-native-fast-image": "^7.0.2",
"react-native-fs": "^2.16.6",
"react-native-gesture-handler": "1.5.2",
"react-native-image-zoom-viewer": "^2.2.5",
"react-native-password-strength-meter": "^0.0.2",
"react-native-phone-input": "lbryio/react-native-phone-input",
"react-native-progress-circle": "2.1.0",
"react-native-reanimated": "1.4.0",
"react-native-safe-area-context": "^0.6.2",
"react-native-screens": "^2.0.0",
"react-native-snackbar": "2.0.4",
"react-native-super-grid": "^3.0.4",
"react-native-vector-icons": "^6.6.0",
"react-native-video": "lbryio/react-native-video#7992ff945872f9bd00a3736d9ff1318f343abf47",
"react-native-webview": "^8.0.2",
"react-navigation": "^4.0.10",
"react-navigation-drawer": "2.3.3",
"react-navigation-redux-helpers": "^3.0.2",
"react-navigation-tabs": "^2.7.0",
"react-navigation-stack": "^1.10.3",
"react-redux": "^5.0.3",
"redux": "^4.0.4",
"redux-persist": "^6.0.0",
"redux-persist-filesystem-storage": "^2.1.0",
"redux-persist-transform-compress": "^4.2.0",
"redux-persist-transform-filter": "0.0.18",
"redux-thunk": "^2.3.0",
"rn-fetch-blob": "0.12.0",
"seedrandom": "3.0.3",
"showdown": "1.9.1"
},
"devDependencies": {
"@babel/core": "^7.6.2",
"babel-eslint": "10.0.2",
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-2": "^6.18.0",
"babel-plugin-module-resolver": "^3.1.1",
"eslint": "^6.5.1",
"eslint-config-standard": "^12.0.0",
"eslint-config-standard-jsx": "^6.0.2",
"eslint-plugin-flowtype": "^2.46.1",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-node": "^8.0.1",
"eslint-plugin-promise": "^4.1.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-standard": "^4.0.0",
"flow-babel-webpack-plugin": "^1.1.1",
"husky": "^0.14.3",
"lint-staged": "^7.0.4",
"metro-react-native-babel-preset": "0.56.3",
"prettier": "^1.11.1",
"@react-native-community/eslint-config": "^0.0.5",
"react-devtools": "^3.6.3",
"reactotron-react-native": "4.0.2",
"reactotron-redux": "3.1.2"
},
"jest": {
"preset": "react-native"
}
}

12
reactotron.js Normal file
View file

@ -0,0 +1,12 @@
import AsyncStorage from '@react-native-community/async-storage';
import Reactotron from 'reactotron-react-native';
import { reactotronRedux } from 'reactotron-redux';
const reactotron = Reactotron
.setAsyncStorageHandler(AsyncStorage) // AsyncStorage would either come from `react-native` or `@react-native-community/async-storage` depending on where you get it from
.configure() // controls connection & communication settings
.useReactNative() // add all built-in react native plugins
.use(reactotronRedux())
.connect();
export default reactotron;

BIN
src/assets/gerbil-happy.png Normal file

Binary file not shown.

After

(image error) Size: 265 KiB

BIN
src/assets/gerbil-sad.png Normal file

Binary file not shown.

After

(image error) Size: 278 KiB

View file

@ -1,10 +1,13 @@
import React from 'react';
import AboutPage from 'page/about';
import ChannelCreatorPage from 'page/channelCreator';
import DiscoverPage from 'page/discover';
import DownloadsPage from 'page/downloads';
import DrawerContent from 'component/drawerContent';
import FilePage from 'page/file';
import LiteFilePage from 'page/liteFile';
import FirstRunScreen from 'page/firstRun';
import InvitesPage from 'page/invites';
import PublishPage from 'page/publish';
import PublishesPage from 'page/publishes';
import RewardsPage from 'page/rewards';
@ -17,17 +20,44 @@ import SubscriptionsPage from 'page/subscriptions';
import TransactionHistoryPage from 'page/transactionHistory';
import VerificationScreen from 'page/verification';
import WalletPage from 'page/wallet';
import { createDrawerNavigator, createStackNavigator, NavigationActions } from 'react-navigation';
import { NavigationActions, StackActions } from 'react-navigation';
import { createDrawerNavigator } from 'react-navigation-drawer';
import { createStackNavigator } from 'react-navigation-stack';
import {
createReduxContainer,
createReactNavigationReduxMiddleware,
createNavigationReducer,
} from 'react-navigation-redux-helpers';
import { connect } from 'react-redux';
import { AppState, BackHandler, Linking, NativeModules, TextInput, ToastAndroid } from 'react-native';
import { selectDrawerStack } from 'redux/selectors/drawer';
import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
import {
AppState,
Alert,
BackHandler,
DeviceEventEmitter,
Linking,
NativeModules,
StatusBar,
TextInput,
ToastAndroid,
} from 'react-native';
import { selectDrawerStack } from 'redux/selectors/drawer';
import {
Lbry,
ACTIONS,
SETTINGS,
doBalanceSubscribe,
doDismissToast,
doPopulateSharedUserState,
doPreferenceGet,
doToast,
selectToast,
} from 'lbry-redux';
import {
Lbryio,
rewards as REWARD_TYPES,
doBlackListedOutpointsSubscribe,
doClaimRewardType,
doFilteredOutpointsSubscribe,
doGetSync,
doUserCheckEmailVerified,
doUserEmailVerify,
@ -35,11 +65,13 @@ import {
selectEmailToVerify,
selectEmailVerifyIsPending,
selectEmailVerifyErrorMessage,
selectHashChanged,
selectUser,
} from 'lbryinc';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doStartDownload, doUpdateDownload, doCompleteDownload } from 'redux/actions/file';
import { makeSelectClientSetting, selectFullscreenMode } from 'redux/selectors/settings';
import { decode as atob } from 'base-64';
import { dispatchNavigateBack, dispatchNavigateToUri } from 'utils/helper';
import { dispatchNavigateBack, dispatchNavigateToUri, transformUrl } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
@ -48,25 +80,20 @@ import NavigationButton from 'component/navigationButton';
import discoverStyle from 'styles/discover';
import searchStyle from 'styles/search';
import SearchRightHeaderIcon from 'component/searchRightHeaderIcon';
import Snackbar from 'react-native-snackbar';
import { doSetSdkReady } from 'redux/actions/settings';
const menuNavigationButton = navigation => (
<NavigationButton
name="bars"
size={24}
style={discoverStyle.drawerMenuButton}
iconStyle={discoverStyle.drawerHamburger}
onPress={() => navigation.openDrawer()}
/>
);
const SYNC_GET_INTERVAL = 1000 * 60 * 5; // every 5 minutes
const discoverStack = createStackNavigator(
{
Discover: {
screen: DiscoverPage,
navigationOptions: ({ navigation }) => ({
title: 'Explore',
Subscriptions: {
screen: SubscriptionsPage,
navigationOptions: {
title: 'Following',
header: null,
}),
drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={drawerIconSize} style={{ color: tintColor }} />,
},
},
File: {
screen: FilePage,
@ -90,7 +117,7 @@ const discoverStack = createStackNavigator(
{
headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }),
}
},
);
discoverStack.navigationOptions = ({ navigation }) => {
@ -124,88 +151,107 @@ const walletStack = createStackNavigator(
{
headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }),
}
},
);
const drawerIconSize = 18;
const drawer = createDrawerNavigator(
{
DiscoverStack: {
screen: discoverStack,
navigationOptions: {
title: 'Explore',
drawerIcon: ({ tintColor }) => <Icon name="home" size={20} style={{ color: tintColor }} />,
title: 'Following',
drawerIcon: ({ tintColor }) => <Icon name="home" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
TrendingStack: {
Discover: {
screen: DiscoverPage,
navigationOptions: ({ navigation }) => ({
title: 'Your Tags',
header: null,
}),
},
Trending: {
screen: TrendingPage,
navigationOptions: {
title: 'Trending',
drawerIcon: ({ tintColor }) => <Icon name="fire" size={20} style={{ color: tintColor }} />,
},
},
MySubscriptionsStack: {
screen: SubscriptionsPage,
navigationOptions: {
title: 'Subscriptions',
drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={20} style={{ color: tintColor }} />,
title: 'All Content',
drawerIcon: ({ tintColor }) => <Icon name="fire" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
WalletStack: {
screen: walletStack,
navigationOptions: {
title: 'Wallet',
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
ChannelCreator: {
screen: ChannelCreatorPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="at" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
Publish: {
screen: PublishPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="upload" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => <Icon name="upload" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
Publishes: {
screen: PublishesPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="cloud-upload-alt" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => (
<Icon name="cloud-upload-alt" size={drawerIconSize} style={{ color: tintColor }} />
),
},
},
Rewards: {
screen: RewardsPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="award" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => <Icon name="award" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
MyLBRYStack: {
Invites: {
screen: InvitesPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="user-friends" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
Downloads: {
screen: DownloadsPage,
navigationOptions: {
title: 'Library',
drawerIcon: ({ tintColor }) => <Icon name="download" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => <Icon name="download" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
Settings: {
screen: SettingsPage,
navigationOptions: {
drawerLockMode: 'locked-closed',
drawerIcon: ({ tintColor }) => <Icon name="cog" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => <Icon name="cog" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
About: {
screen: AboutPage,
navigationOptions: {
drawerLockMode: 'locked-closed',
drawerIcon: ({ tintColor }) => <Icon name="info" size={20} style={{ color: tintColor }} />,
drawerIcon: ({ tintColor }) => <Icon name="info" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
},
{
drawerWidth: 300,
drawerWidth: 299,
drawerBackgroundColor: 'transparent',
headerMode: 'none',
backBehavior: 'none',
unmountInactiveRoutes: true,
contentComponent: DrawerContent,
overlayColor: 'rgba(0, 0, 0, 0.7)',
contentOptions: {
activeTintColor: Colors.LbryGreen,
labelStyle: discoverStyle.menuText,
},
}
},
);
const mainStackNavigator = new createStackNavigator(
@ -231,10 +277,16 @@ const mainStackNavigator = new createStackNavigator(
drawerLockMode: 'locked-closed',
},
},
LiteFile: {
screen: LiteFilePage,
navigationOptions: {
drawerLockMode: 'locked-closed',
},
},
},
{
headerMode: 'none',
}
},
);
export const AppNavigator = mainStackNavigator;
@ -253,9 +305,11 @@ class AppWithNavigationState extends React.Component {
constructor() {
super();
this.emailVerifyCheckInterval = null;
this.syncGetInterval = null;
this.state = {
emailVerifyDone: false,
verifyPending: false,
syncHashChanged: false,
};
}
@ -265,29 +319,36 @@ class AppWithNavigationState extends React.Component {
'hardwareBackPress',
function() {
const { dispatch, nav, drawerStack } = this.props;
// There should be a better way to check this
if (nav.routes.length > 0) {
if (nav.routes[0].routeName === 'Main') {
const mainRoute = nav.routes[0];
if (
mainRoute.index > 0 ||
mainRoute.routes[0].index > 0 /* Discover stack index */ ||
mainRoute.routes[4].index > 0 /* Wallet stack index */ ||
mainRoute.index >= 5 /* Settings and About screens */
) {
dispatchNavigateBack(dispatch, nav, drawerStack);
return true;
}
}
if (drawerStack.length > 1) {
dispatchNavigateBack(dispatch, nav, drawerStack);
return true;
}
return false;
}.bind(this)
}.bind(this),
);
}
componentDidMount() {
const { dispatch, user } = this.props;
this.emailVerifyCheckInterval = setInterval(() => this.checkEmailVerification(), 5000);
Linking.addEventListener('url', this._handleUrl);
DeviceEventEmitter.addListener('onSdkReady', this.handleSdkReady);
DeviceEventEmitter.addListener('onDownloadAborted', this.handleDownloadAborted);
DeviceEventEmitter.addListener('onDownloadStarted', this.handleDownloadStarted);
DeviceEventEmitter.addListener('onDownloadUpdated', this.handleDownloadUpdated);
DeviceEventEmitter.addListener('onDownloadCompleted', this.handleDownloadCompleted);
// call /sync/get with interval
this.syncGetInterval = setInterval(() => {
this.setState({ syncHashChanged: false }); // reset local state
if (user && user.has_verified_email) {
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(walletPassword => {
dispatch(doGetSync(walletPassword, () => this.getUserSettings()));
});
}
}, SYNC_GET_INTERVAL);
}
checkEmailVerification = () => {
@ -300,27 +361,152 @@ class AppWithNavigationState extends React.Component {
});
};
getUserSettings = () => {
const { dispatch } = this.props;
doPreferenceGet(
'shared',
preference => {
dispatch(doPopulateSharedUserState(preference));
},
error => {
/* failed */
},
);
};
checkNewAndroidReward = () => {
const { dispatch, doToast } = this.props;
const claimRewardCallback = err => {
if (err) {
// an error occurred, do not display anything
return;
}
// reward successfully claimed
NativeModules.UtilityModule.setNativeBooleanSetting(Constants.SETTING_NEW_ANDROID_REWARD_CLAIMED, true);
};
NativeModules.UtilityModule.getNativeBooleanSetting(Constants.SETTING_NEW_ANDROID_REWARD_CLAIMED, false).then(
rewardClaimed => {
if (!rewardClaimed) {
dispatch(
doClaimRewardType(REWARD_TYPES.TYPE_NEW_ANDROID, { notifyError: false, callback: claimRewardCallback }),
);
}
},
);
};
handleSdkReady = () => {
const { dispatch } = this.props;
dispatch(doSetSdkReady());
dispatch(doBalanceSubscribe());
dispatch(doBlackListedOutpointsSubscribe());
dispatch(doFilteredOutpointsSubscribe());
Lbry.wallet_status().then(secureWalletStatus => {
// For now, automatically unlock the wallet if a password is set so that downloads work
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password => {
if ((secureWalletStatus.is_encrypted && !secureWalletStatus.is_locked) || secureWalletStatus.is_locked) {
this.setState({
message: __('Unlocking account'),
details: __('Decrypting wallet'),
});
// unlock the wallet and then finish the splash screen
Lbry.wallet_unlock({ password: password || '' }).then(unlocked => {
if (unlocked) {
} else {
// this.handleAccountUnlockFailed();
}
});
}
});
});
this.checkNewAndroidReward();
};
handleAccountUnlockFailed() {
const { dispatch } = this.props;
dispatch(
doToast({
message: __(
'Your wallet failed to unlock, which means you may not be able to play any videos or access your funds. Restart the app to fix this.',
),
isError: true,
}),
);
}
handleDownloadStarted = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo } = evt;
dispatch(doStartDownload(uri, outpoint, fileInfo));
};
handleDownloadUpdated = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo, progress } = evt;
dispatch(doUpdateDownload(uri, outpoint, fileInfo, progress));
};
handleDownloadCompleted = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo } = evt;
dispatch(doCompleteDownload(uri, outpoint, fileInfo));
};
handleDownloadAborted = evt => {
const { dispatch } = this.props;
const { uri, outpoint } = evt;
dispatch({
type: ACTIONS.DOWNLOADING_CANCELED,
data: { uri, outpoint },
});
dispatch({
type: ACTIONS.FILE_DELETE,
data: {
outpoint,
},
});
};
componentWillUnmount() {
DeviceEventEmitter.removeListener('onSdkReady', this.handleSdkReady);
DeviceEventEmitter.removeListener('onDownloadAborted', this.handleDownloadAborted);
DeviceEventEmitter.removeListener('onDownloadStarted', this.handleDownloadStarted);
DeviceEventEmitter.removeListener('onDownloadUpdated', this.handleDownloadUpdated);
DeviceEventEmitter.removeListener('onDownloadCompleted', this.handleDownloadCompleted);
AppState.removeEventListener('change', this._handleAppStateChange);
BackHandler.removeEventListener('hardwareBackPress');
Linking.removeEventListener('url', this._handleUrl);
if (this.emailVerifyCheckInterval > -1) {
clearInterval(this.emailVerifyCheckInterval);
}
if (this.syncGetInterval > -1) {
clearInterval(this.syncGetInterval);
}
}
componentDidUpdate() {
const { dispatch, user } = this.props;
const { dispatch, user, hashChanged } = this.props;
if (this.state.verifyPending && this.emailVerifyCheckInterval > 0 && user && user.has_verified_email) {
clearInterval(this.emailVerifyCheckInterval);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'false');
this.setState({ verifyPending: false });
ToastAndroid.show('Your email address was successfully verified.', ToastAndroid.LONG);
NativeModules.Firebase.track('email_verified', { email: user.primary_email });
Snackbar.show({ title: __('Your email address was successfully verified.'), duration: Snackbar.LENGTH_LONG });
// upon successful email verification, do wallet sync (if password has been set)
NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => {
if (walletPassword && walletPassword.trim().length > 0) {
dispatch(doGetSync(walletPassword));
}
});
// get user settings after email verification
this.getUserSettings();
}
if (hashChanged && !this.state.syncHashChanged) {
this.setState({ syncHashChanged: true });
this.getUserSettings();
}
}
@ -329,7 +515,7 @@ class AppWithNavigationState extends React.Component {
const { toast, emailToVerify, emailVerifyPending, emailVerifyErrorMessage, user } = nextProps;
if (toast) {
const { message } = toast;
const { message, isError } = toast;
let currentDisplayType;
if (!currentDisplayType && message) {
// default to toast if no display type set and there is a message specified
@ -337,7 +523,14 @@ class AppWithNavigationState extends React.Component {
}
if (currentDisplayType === 'toast') {
ToastAndroid.show(message, ToastAndroid.LONG);
const props = {
title: message,
duration: Snackbar.LENGTH_LONG,
};
if (isError) {
props.backgroundColor = Colors.Red;
}
Snackbar.show(props);
}
dispatch(doDismissToast());
@ -349,7 +542,7 @@ class AppWithNavigationState extends React.Component {
this.setState({ emailVerifyDone: true });
const message = emailVerifyErrorMessage
? String(emailVerifyErrorMessage)
: 'Your email address was successfully verified.';
: __('Your email address was successfully verified.');
if (!emailVerifyErrorMessage) {
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
}
@ -384,6 +577,26 @@ class AppWithNavigationState extends React.Component {
if (backgroundPlayEnabled || NativeModules.BackgroundMedia) {
NativeModules.BackgroundMedia.hidePlaybackNotification();
}
// check fullscreen mode and show / hide navigation bar accordingly
this.checkFullscreenMode();
}
};
checkFullscreenMode = () => {
const { fullscreenMode } = this.props;
StatusBar.setHidden(fullscreenMode);
if (fullscreenMode) {
// fullscreen, so change orientation to landscape mode
NativeModules.ScreenOrientation.lockOrientationLandscape();
// hide the navigation bar (on devices that have the soft navigation bar)
NativeModules.UtilityModule.hideNavigationBar();
} else {
// Switch back to portrait mode when the media is not fullscreen
NativeModules.ScreenOrientation.lockOrientationPortrait();
// hide the navigation bar (on devices that have the soft navigation bar)
NativeModules.UtilityModule.showNavigationBar();
}
};
@ -396,7 +609,7 @@ class AppWithNavigationState extends React.Component {
try {
verification = JSON.parse(atob(evt.url.substring(15)));
} catch (error) {
console.log(error);
// console.log(error);
}
if (verification.token && verification.recaptcha) {
@ -404,7 +617,7 @@ class AppWithNavigationState extends React.Component {
try {
dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
} catch (error) {
const message = 'Invalid Verification Token';
const message = __('Invalid Verification Token');
dispatch(doUserEmailVerifyFailure(message));
dispatch(doToast({ message }));
}
@ -412,11 +625,11 @@ class AppWithNavigationState extends React.Component {
dispatch(
doToast({
message: 'Invalid Verification URI',
})
}),
);
}
} else {
dispatchNavigateToUri(dispatch, nav, evt.url);
dispatchNavigateToUri(dispatch, nav, transformUrl(evt.url));
}
}
};
@ -428,6 +641,7 @@ class AppWithNavigationState extends React.Component {
const mapStateToProps = state => ({
backgroundPlayEnabled: makeSelectClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED)(state),
hashChanged: selectHashChanged(state),
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
nav: state.nav,
toast: selectToast(state),
@ -437,6 +651,7 @@ const mapStateToProps = state => ({
emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
user: selectUser(state),
fullscreenMode: selectFullscreenMode(state),
});
export default connect(mapStateToProps)(AppWithNavigationState);

View file

@ -15,7 +15,7 @@ export default class Address extends React.PureComponent<Props> {
return (
<View style={[walletStyle.row, style]}>
<Text selectable={true} numberOfLines={1} style={walletStyle.address}>
<Text selectable numberOfLines={1} style={walletStyle.address}>
{address || ''}
</Text>
<Button
@ -24,7 +24,7 @@ export default class Address extends React.PureComponent<Props> {
onPress={() => {
Clipboard.setString(address);
doToast({
message: 'Address copied',
message: __('Address copied'),
});
}}
/>

View file

@ -34,16 +34,11 @@ export default class Button extends React.PureComponent {
}
let renderIcon = (
<Icon name={icon} size={18} color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White} />
<Icon name={icon} size={16} color={iconColor || (theme === 'light' ? Colors.DarkGrey : Colors.White)} />
);
if (solid) {
renderIcon = (
<Icon
name={icon}
size={18}
color={iconColor ? iconColor : 'light' === theme ? Colors.DarkGrey : Colors.White}
solid
/>
<Icon name={icon} size={16} color={iconColor || (theme === 'light' ? Colors.DarkGrey : Colors.White)} solid />
);
}

View file

@ -1,4 +0,0 @@
import { connect } from 'react-redux';
import CategoryList from './view';
export default connect()(CategoryList);

View file

@ -1,39 +0,0 @@
import React from 'react';
import NavigationActions from 'react-navigation';
import { FlatList, Text, View } from 'react-native';
import { normalizeURI } from 'lbry-redux';
import FileItem from '/component/fileItem';
import discoverStyle from 'styles/discover';
class CategoryList extends React.PureComponent {
render() {
const { category, categoryMap, navigation } = this.props;
return (
<FlatList
style={discoverStyle.horizontalScrollContainer}
contentContainerStyle={discoverStyle.horizontalScrollPadding}
initialNumToRender={3}
maxToRenderPerBatch={3}
removeClippedSubviews={true}
renderItem={({ item }) => (
<FileItem
style={discoverStyle.fileItem}
mediaStyle={discoverStyle.fileItemMedia}
key={item}
uri={normalizeURI(item)}
navigation={navigation}
showDetails={true}
compactView={false}
/>
)}
horizontal={true}
showsHorizontalScrollIndicator={false}
data={categoryMap[category]}
keyExtractor={(item, index) => item}
/>
);
}
}
export default CategoryList;

View file

@ -1,10 +1,17 @@
import { connect } from 'react-redux';
import { doResolveUri, makeSelectClaimForUri, makeSelectThumbnailForUri, makeSelectIsUriResolving } from 'lbry-redux';
import {
doResolveUri,
makeSelectClaimForUri,
makeSelectThumbnailForUri,
makeSelectTitleForUri,
makeSelectIsUriResolving,
} from 'lbry-redux';
import ChannelIconItem from './view';
const select = (state, props) => ({
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
});
@ -12,7 +19,4 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
});
export default connect(
select,
perform
)(ChannelIconItem);
export default connect(select, perform)(ChannelIconItem);

View file

@ -1,9 +1,40 @@
import React from 'react';
import { ActivityIndicator, Image, Text, TouchableOpacity, View } from 'react-native';
import Colors from 'styles/colors';
import autothumbStyle from 'styles/autothumb';
import channelIconStyle from 'styles/channelIcon';
export default class ChannelIconItem extends React.PureComponent {
static AUTO_THUMB_STYLES = [
autothumbStyle.autothumbPurple,
autothumbStyle.autothumbRed,
autothumbStyle.autothumbPink,
autothumbStyle.autothumbIndigo,
autothumbStyle.autothumbBlue,
autothumbStyle.autothumbLightBlue,
autothumbStyle.autothumbCyan,
autothumbStyle.autothumbGreen,
autothumbStyle.autothumbYellow,
autothumbStyle.autothumbOrange,
autothumbStyle.autothumbDeepPurple,
autothumbStyle.autothumbAmber,
autothumbStyle.autothumbLime,
autothumbStyle.autothumbLightGreen,
autothumbStyle.autothumbDeepOrange,
autothumbStyle.autothumbBrown,
];
state = {
autoStyle: null,
};
componentWillMount() {
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
}
componentDidMount() {
const { claim, isPlaceholder, uri, resolveUri } = this.props;
if (!claim && !isPlaceholder) {
@ -13,36 +44,40 @@ export default class ChannelIconItem extends React.PureComponent {
render() {
const { claim, isPlaceholder, isResolvingUri, onPress, thumbnail, title } = this.props;
const displayName = title || (claim ? claim.name : '');
const substrIndex = displayName.startsWith('@') ? 1 : 0;
return (
<TouchableOpacity style={channelIconStyle.container} onPress={onPress}>
{isResolvingUri && (
<View style={channelIconStyle.centered}>
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
</View>
)}
<View
style={[
channelIconStyle.thumbnailContainer,
isPlaceholder ? channelIconStyle.borderedThumbnailContainer : null,
isPlaceholder ? null : this.state.autoStyle,
]}
>
{isPlaceholder && (
<View style={channelIconStyle.centered}>
<Text style={channelIconStyle.placeholderText}>ALL</Text>
<Text style={channelIconStyle.placeholderText}>{__('ALL')}</Text>
</View>
)}
{!isPlaceholder && (
<Image
style={channelIconStyle.thumbnail}
resizeMode={'cover'}
source={thumbnail ? { uri: thumbnail } : require('../../assets/default_avatar.jpg')}
/>
{!isPlaceholder && thumbnail && (
<Image style={channelIconStyle.thumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} />
)}
{!isPlaceholder && !thumbnail && (
<Text style={channelIconStyle.autothumbCharacter}>
{displayName.substring(substrIndex, substrIndex + 1).toUpperCase()}
</Text>
)}
</View>
{!isPlaceholder && (
<Text style={channelIconStyle.title} numberOfLines={1}>
{title || (claim ? claim.name : '')}
{displayName}
</Text>
)}
</TouchableOpacity>

View file

@ -0,0 +1,4 @@
import { connect } from 'react-redux';
import ChannelRewardsDriver from './view';
export default connect()(ChannelRewardsDriver);

View file

@ -0,0 +1,24 @@
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import publishStyle from 'styles/publish';
class ChannelRewardsDriver extends React.PureComponent<Props> {
render() {
const { navigation } = this.props;
return (
<TouchableOpacity style={publishStyle.rewardDriverCard} onPress={() => navigation.navigate('Rewards')}>
<Icon name="award" size={16} style={publishStyle.rewardIcon} />
<Text style={publishStyle.rewardDriverText}>
{__('Channel creation requires credits.')}
{'\n'}
{__('Tap here to get some for free.')}
</Text>
</TouchableOpacity>
);
}
}
export default ChannelRewardsDriver;

View file

@ -7,6 +7,7 @@ import {
doCreateChannel,
doToast,
} from 'lbry-redux';
import { doGetSync } from 'lbryinc';
import ChannelSelector from './view';
const select = state => ({
@ -18,10 +19,11 @@ const select = state => ({
const perform = dispatch => ({
notify: data => dispatch(doToast(data)),
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
getSync: (password, callback) => dispatch(doGetSync(password, callback)),
});
export default connect(
select,
perform
perform,
)(ChannelSelector);

View file

@ -1,9 +1,10 @@
import React from 'react';
import { CLAIM_VALUES, isNameValid } from 'lbry-redux';
import { ActivityIndicator, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { CLAIM_VALUES, formatCredits, isNameValid } from 'lbry-redux';
import { ActivityIndicator, NativeModules, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { logPublish } from 'utils/helper';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import channelSelectorStyle from 'styles/channelSelector';
@ -13,6 +14,7 @@ export default class ChannelSelector extends React.PureComponent {
super(props);
this.state = {
creditsInputFocused: false,
currentSelectedValue: Constants.ITEM_ANONYMOUS,
newChannelName: '',
newChannelBid: 0.1,
@ -26,10 +28,24 @@ export default class ChannelSelector extends React.PureComponent {
}
componentDidMount() {
const { channels, fetchChannelListMine, fetchingChannels } = this.props;
if (!channels.length && !fetchingChannels) {
const { channels = [], channelName, fetchChannelListMine, fetchingChannels } = this.props;
if ((!channels || channels.length === 0) && !fetchingChannels) {
fetchChannelListMine();
}
this.setState({ currentSelectedValue: channelName });
}
componentWillReceiveProps(nextProps) {
const { channels: prevChannels = [], channelName: prevChannelName } = this.props;
const { channels = [], channelName } = nextProps;
if (channels && channels.length !== prevChannels.length && channelName !== this.state.currentSelectedValue) {
this.setState({ currentSelectedValue: prevChannelName });
}
if (channelName !== prevChannelName) {
this.setState({ currentSelectedValue: channelName });
}
}
handleCreateCancel = () => {
@ -54,13 +70,13 @@ export default class ChannelSelector extends React.PureComponent {
if (channel === CLAIM_VALUES.CHANNEL_NEW) {
this.setState({ addingChannel: true });
if (onChannelChange) {
onChannelChange(channel);
onChannelChange(value);
}
this.handleNewChannelBidChange(newChannelBid);
} else {
this.setState({ addingChannel: false });
if (onChannelChange) {
onChannelChange(channel);
onChannelChange(value);
}
}
};
@ -68,14 +84,22 @@ export default class ChannelSelector extends React.PureComponent {
handleNewChannelNameChange = value => {
const { notify } = this.props;
let newChannelName = value;
let newChannelName = value,
newChannelNameError = '';
if (newChannelName.startsWith('@')) {
newChannelName = newChannelName.slice(1);
}
if (newChannelName.trim().length > 0 && !isNameValid(newChannelName)) {
newChannelNameError = __('Your channel name contains invalid characters.');
} else if (this.channelExists(newChannelName)) {
newChannelNameError = __('You have already created a channel with the same name.');
}
this.setState({
newChannelName,
newChannelNameError,
});
};
@ -99,21 +123,21 @@ export default class ChannelSelector extends React.PureComponent {
};
handleCreateChannelClick = () => {
const { balance, createChannel, onChannelChange, notify } = this.props;
const { balance, createChannel, getSync, onChannelChange, notify } = this.props;
const { newChannelBid, newChannelName } = this.state;
if (newChannelName.trim().length === 0 || !isNameValid(newChannelName.substr(1), false)) {
notify({ message: 'Your channel name contains invalid characters.' });
notify({ message: __('Your channel name contains invalid characters.') });
return;
}
if (this.channelExists(newChannelName)) {
notify({ message: 'You have already created a channel with the same name.' });
notify({ message: __('You have already created a channel with the same name.') });
return;
}
if (newChannelBid > balance) {
notify({ message: 'Deposit cannot be higher than your balance' });
notify({ message: __('Deposit cannot be higher than your balance') });
return;
}
@ -124,21 +148,25 @@ export default class ChannelSelector extends React.PureComponent {
createChannelError: undefined,
});
const success = () => {
const success = channelClaim => {
this.setState({
creatingChannel: false,
addingChannel: false,
currentSelectedValue: channelName,
showCreateChannel: false,
});
logPublish(channelClaim);
if (onChannelChange) {
onChannelChange(channelName);
}
// sync wallet
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password => getSync(password));
};
const failure = () => {
notify({ message: 'Unable to create channel due to an internal error.' });
notify({ message: __('Unable to create channel due to an internal error.') });
this.setState({
creatingChannel: false,
});
@ -149,12 +177,14 @@ export default class ChannelSelector extends React.PureComponent {
channelExists = name => {
const { channels = [] } = this.props;
for (let channel of channels) {
if (
name.toLowerCase() === channel.name.toLowerCase() ||
`@${name}`.toLowerCase() === channel.name.toLowerCase()
) {
return true;
if (channels) {
for (let channel of channels) {
if (
name.toLowerCase() === channel.name.toLowerCase() ||
`@${name}`.toLowerCase() === channel.name.toLowerCase()
) {
return true;
}
}
}
@ -163,10 +193,12 @@ export default class ChannelSelector extends React.PureComponent {
render() {
const channel = this.state.addingChannel ? 'new' : this.props.channel;
const { fetchingChannels, channels = [] } = this.props;
const pickerItems = [{ name: Constants.ITEM_ANONYMOUS }, { name: Constants.ITEM_CREATE_A_CHANNEL }].concat(
channels
);
const { balance, enabled, fetchingChannels, channels = [], showAnonymous } = this.props;
const pickerItems = (showAnonymous
? [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL]
: [Constants.ITEM_CREATE_A_CHANNEL]
).concat(channels ? channels.map(ch => ch.name) : []);
const {
newChannelName,
newChannelNameError,
@ -175,22 +207,28 @@ export default class ChannelSelector extends React.PureComponent {
creatingChannel,
createChannelError,
addingChannel,
showCreateChannel,
} = this.state;
return (
<View style={channelSelectorStyle.container}>
<Picker
enabled={enabled}
selectedValue={this.state.currentSelectedValue}
style={channelSelectorStyle.channelPicker}
itemStyle={channelSelectorStyle.channelPickerItem}
onValueChange={this.handlePickerValueChange}
>
{pickerItems.map(item => (
<Picker.Item label={item.name} value={item.name} key={item.name} />
<Picker.Item
label={[Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].includes(item) ? __(item) : item}
value={item}
key={item}
/>
))}
</Picker>
{this.state.showCreateChannel && (
{showCreateChannel && (
<View style={channelSelectorStyle.createChannelContainer}>
<View style={channelSelectorStyle.channelInputContainer}>
<Text style={channelSelectorStyle.channelAt}>@</Text>
@ -199,35 +237,46 @@ export default class ChannelSelector extends React.PureComponent {
style={channelSelectorStyle.channelNameInput}
value={this.state.newChannelName}
onChangeText={this.handleNewChannelNameChange}
placeholder={'Channel name'}
placeholder={__('Channel name')}
underlineColorAndroid={Colors.NextLbryGreen}
/>
</View>
{newChannelNameError.length > 0 && (
<Text style={channelSelectorStyle.inlineError}>{newChannelNameError}</Text>
)}
<View style={channelSelectorStyle.bidRow}>
<Text style={channelSelectorStyle.label}>Deposit</Text>
<Text style={channelSelectorStyle.label}>{__('Deposit')}</Text>
<TextInput
style={channelSelectorStyle.bidAmountInput}
value={String(this.state.newChannelBid)}
value={String(newChannelBid)}
onChangeText={this.handleNewChannelBidChange}
onFocus={() => this.setState({ creditsInputFocused: true })}
onBlur={() => this.setState({ creditsInputFocused: false })}
placeholder={'0.00'}
keyboardType={'number-pad'}
underlineColorAndroid={Colors.NextLbryGreen}
/>
<Text style={channelSelectorStyle.currency}>LBC</Text>
<View style={channelSelectorStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={channelSelectorStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
<Text style={channelSelectorStyle.helpText}>
This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.
{__('This LBC remains yours. It is a deposit to reserve the name and can be undone at any time.')}
</Text>
<View style={channelSelectorStyle.buttonContainer}>
{creatingChannel && <ActivityIndicator size={'small'} color={Colors.LbryGreen} />}
{creatingChannel && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
{!creatingChannel && (
<View style={channelSelectorStyle.buttons}>
<Link style={channelSelectorStyle.cancelLink} text="Cancel" onPress={this.handleCreateCancel} />
<Link style={channelSelectorStyle.cancelLink} text={__('Cancel')} onPress={this.handleCreateCancel} />
<Button
style={channelSelectorStyle.createButton}
disabled={!(this.state.newChannelName.trim().length > 0 && this.state.newChannelBid > 0)}
text="Create"
disabled={!(newChannelName.trim().length > 0 && newChannelBid > 0)}
text={__('Create')}
onPress={this.handleCreateChannelClick}
/>
</View>

View file

@ -2,47 +2,25 @@ import { connect } from 'react-redux';
import {
MATURE_TAGS,
doClaimSearch,
doClaimSearchByTags,
makeSelectClaimSearchUrisForTags,
makeSelectFetchingClaimSearchForTags,
selectClaimSearchByQuery,
selectClaimSearchByQueryLastPageReached,
selectFetchingClaimSearchByQuery,
selectFetchingClaimSearch,
selectLastClaimSearchUris,
} from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import ClaimList from './view';
const select = (state, props) => {
return {
loading: makeSelectFetchingClaimSearchForTags(props.tags)(state),
uris: makeSelectClaimSearchUrisForTags(props.tags)(state),
// for subscriptions
claimSearchLoading: selectFetchingClaimSearch(state),
claimSearchUris: selectLastClaimSearchUris(state),
};
};
const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(Constants.DEFAULT_PAGE_SIZE, options)),
searchByTags: (tags, orderBy = Constants.DEFAULT_ORDER_BY, page = 1, additionalOptions = {}) =>
dispatch(
doClaimSearchByTags(
tags,
Constants.DEFAULT_PAGE_SIZE,
Object.assign(
{},
{
no_totals: true,
order_by: orderBy,
page,
not_tags: MATURE_TAGS,
},
additionalOptions
)
)
),
const select = state => ({
claimSearchByQuery: selectClaimSearchByQuery(state),
lastPageReached: selectClaimSearchByQueryLastPageReached(state),
loadingByQuery: selectFetchingClaimSearchByQuery(state),
loading: selectFetchingClaimSearch(state),
showNsfwContent: selectShowNsfw(state),
});
export default connect(
select,
perform
)(ClaimList);
const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(select, perform)(ClaimList);

View file

@ -1,7 +1,7 @@
import React from 'react';
import NavigationActions from 'react-navigation';
import { ActivityIndicator, FlatList, Text, TouchableOpacity, View } from 'react-native';
import { MATURE_TAGS, normalizeURI } from 'lbry-redux';
import { MATURE_TAGS, normalizeURI, createNormalizedClaimSearchKey } from 'lbry-redux';
import _ from 'lodash';
import FileItem from 'component/fileItem';
import FileListItem from 'component/fileListItem';
@ -21,94 +21,82 @@ class ClaimList extends React.PureComponent {
state = {
currentPage: 1, // initial page load is page 1
subscriptionsView: false, // whether or not this claim list is for subscriptions
trendingForAllView: false,
lastPageReached: false,
};
componentDidMount() {
const {
channelIds,
trendingForAll,
claimSearch,
orderBy = Constants.DEFAULT_ORDER_BY,
searchByTags,
tags,
time,
} = this.props;
if (channelIds || trendingForAll) {
const options = {
order_by: orderBy,
no_totals: true,
not_tags: MATURE_TAGS,
page: this.state.currentPage,
};
if (channelIds) {
this.setState({ subscriptionsView: true });
options.channel_ids = channelIds;
} else if (trendingForAll) {
this.setState({ trendingForAllView: true });
}
const { channelIds } = this.props;
claimSearch(options);
} else if (tags && tags.length > 0) {
const additionalOptions = {};
if (orderBy && orderBy[0] === Constants.ORDER_BY_EFFECTIVE_AMOUNT && Constants.TIME_ALL !== time) {
additionalOptions.release_time = this.getReleaseTimeOption(time);
}
searchByTags(tags, orderBy, this.state.currentPage, additionalOptions);
if (channelIds) {
this.setState({ subscriptionsView: true });
}
this.doClaimSearch();
}
componentWillReceiveProps(nextProps) {
componentDidUpdate(prevProps) {
const {
claimSearch,
claimSearchByQuery: prevClaimSearchByQuery,
orderBy: prevOrderBy,
searchByTags,
tags: prevTags,
channelIds: prevChannelIds,
trendingForAll: prevTrendingForAll,
time: prevTime,
} = this.props;
const { orderBy, tags, channelIds, trendingForAll, time } = nextProps;
} = prevProps;
const { claimSearchByQuery, orderBy, tags, channelIds, time } = this.props;
if (
!_.isEqual(orderBy, prevOrderBy) ||
!_.isEqual(tags, prevTags) ||
!_.isEqual(channelIds, prevChannelIds) ||
time !== prevTime ||
trendingForAll !== prevTrendingForAll
time !== prevTime
) {
// reset to page 1 because the order, tags or channelIds changed
this.setState({ currentPage: 1 }, () => {
if (this.scrollView) {
this.scrollView.scrollToOffset({ animated: true, offset: 0 });
}
if (trendingForAll || (prevChannelIds && channelIds)) {
const options = {
order_by: orderBy,
no_totals: true,
not_tags: MATURE_TAGS,
page: this.state.currentPage,
};
if (prevChannelIds && channelIds) {
if (channelIds) {
this.setState({ subscriptionsView: true });
options.channel_ids = channelIds;
}
if (trendingForAll) {
this.setState({ trendingForAllView: true });
}
claimSearch(options);
} else if (tags && tags.length > 0) {
this.setState({ subscriptionsView: false, trendingForAllView: false });
const additionalOptions = {};
if (orderBy && orderBy[0] === Constants.ORDER_BY_EFFECTIVE_AMOUNT && Constants.TIME_ALL !== time) {
additionalOptions.release_time = this.getReleaseTimeOption(time);
}
searchByTags(tags, orderBy, this.state.currentPage, additionalOptions);
this.setState({ subscriptionsView: false });
}
this.doClaimSearch();
});
}
}
buildClaimSearchOptions() {
const { orderBy, channelIds, showNsfwContent, tags, time } = this.props;
const { currentPage, subscriptionsView } = this.state;
const options = {
order_by: orderBy,
no_totals: true,
page: currentPage,
page_size: Constants.DEFAULT_PAGE_SIZE,
};
if (channelIds) {
options.channel_ids = channelIds;
} else if (tags && tags.length > 0) {
options.any_tags = tags;
}
if (!showNsfwContent) {
options.not_tags = MATURE_TAGS;
}
if (orderBy && orderBy[0] === Constants.ORDER_BY_EFFECTIVE_AMOUNT && Constants.TIME_ALL !== time) {
options.release_time = this.getReleaseTimeOption(time);
}
return options;
}
getReleaseTimeOption = time => {
return `>${Math.floor(
moment()
@ -117,36 +105,27 @@ class ClaimList extends React.PureComponent {
)}`;
};
doClaimSearch() {
const { claimSearch } = this.props;
const options = this.buildClaimSearchOptions();
claimSearch(options);
}
handleVerticalEndReached = () => {
// fetch more content
const { channelIds, claimSearch, claimSearchUris, orderBy, searchByTags, tags, time, uris } = this.props;
const { subscriptionsView, trendingForAllView } = this.state;
if ((claimSearchUris && claimSearchUris.length >= softLimit) || (uris && uris.length >= softLimit)) {
// don't fetch more than the specified limit to be displayed
const { claimSearchByQuery, lastPageReached } = this.props;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
const uris = claimSearchByQuery[claimSearchKey];
if (
lastPageReached[claimSearchKey] ||
((uris.length > 0 && uris.length < Constants.DEFAULT_PAGE_SIZE) || uris.length >= softLimit)
) {
return;
}
this.setState({ currentPage: this.state.currentPage + 1 }, () => {
if (subscriptionsView || trendingForAllView) {
const options = {
order_by: orderBy,
no_totals: true,
not_tags: MATURE_TAGS,
page: this.state.currentPage,
};
if (subscriptionsView) {
options.channel_ids = channelIds;
}
claimSearch(options);
} else {
const additionalOptions = {};
if (orderBy && orderBy[0] === Constants.ORDER_BY_EFFECTIVE_AMOUNT && Constants.TIME_ALL !== time) {
additionalOptions.release_time = this.getReleaseTimeOption(time);
}
searchByTags(tags, orderBy, this.state.currentPage, additionalOptions);
}
});
this.setState({ currentPage: this.state.currentPage + 1 }, () => this.doClaimSearch());
};
appendMorePlaceholder = items => {
@ -161,35 +140,79 @@ class ClaimList extends React.PureComponent {
if (tags.length === 1) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TAG, key: 'tagPage', params: { tag: tags[0] } });
} else {
navigation.navigate({ routeName: Constants.FULL_ROUTE_NAME_TRENDING });
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_TRENDING, params: { filterForTags: true } });
}
};
renderMorePlaceholder = () => {
return (
<TouchableOpacity style={discoverStyle.fileItemMore} onPress={this.onMorePressed}>
<Text style={discoverStyle.moreText}>more</Text>
<Text style={discoverStyle.moreText}>{__('more')}</Text>
<Icon style={discoverStyle.moreIcon} name={'angle-double-down'} color={Colors.White} size={16} />
</TouchableOpacity>
);
};
verticalListEmptyComponent = () => {
return (
<Text style={claimListStyle.noContentText}>
{__('No content to display at this time. Please check back later.')}
</Text>
);
};
renderVerticalItem = ({ item }) => {
const { hideChannel, navigation } = this.props;
return (
<FileListItem
key={item}
uri={item}
hideChannel={hideChannel}
style={claimListStyle.verticalListItem}
navigation={navigation}
/>
);
};
renderHorizontalItem = ({ item }) => {
const { navigation } = this.props;
return item === Constants.MORE_PLACEHOLDER ? (
this.renderMorePlaceholder()
) : (
<FileItem
style={discoverStyle.fileItem}
mediaStyle={discoverStyle.fileItemMedia}
key={item}
uri={normalizeURI(item)}
navigation={navigation}
showDetails
compactView={false}
/>
);
};
render() {
const {
ListHeaderComponent,
loading,
claimSearchLoading,
claimSearchUris,
morePlaceholder,
navigation,
orientation = Constants.ORIENTATION_VERTICAL,
style,
uris,
claimSearchByQuery,
} = this.props;
const { subscriptionsView, trendingForAllView } = this.state;
const { subscriptionsView } = this.state;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
let uris = claimSearchByQuery[claimSearchKey];
if (uris) {
uris = uris.filter(uri => uri && uri.length > 0);
}
if (Constants.ORIENTATION_VERTICAL === orientation) {
const data = subscriptionsView || trendingForAllView ? claimSearchUris : uris;
return (
<View style={style}>
<FlatList
@ -197,22 +220,21 @@ class ClaimList extends React.PureComponent {
this.scrollView = ref;
}}
ListHeaderComponent={ListHeaderComponent}
ListEmptyComponent={loading ? null : this.verticalListEmptyComponent}
style={claimListStyle.verticalScrollContainer}
contentContainerStyle={claimListStyle.verticalScrollPadding}
initialNumToRender={8}
maxToRenderPerBatch={24}
initialNumToRender={10}
maxToRenderPerBatch={20}
removeClippedSubviews
renderItem={({ item }) => (
<FileListItem key={item} uri={item} style={claimListStyle.verticalListItem} navigation={navigation} />
)}
data={data}
renderItem={this.renderVerticalItem}
data={uris}
keyExtractor={(item, index) => item}
onEndReached={this.handleVerticalEndReached}
onEndReachedThreshold={0.9}
onEndReachedThreshold={0.2}
/>
{(((subscriptionsView || trendingForAllView) && claimSearchLoading) || loading) && (
{loading && (
<View style={claimListStyle.verticalLoading}>
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
</View>
)}
</View>
@ -223,7 +245,7 @@ class ClaimList extends React.PureComponent {
if (loading) {
return (
<View style={discoverStyle.listLoading}>
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
</View>
);
}
@ -235,21 +257,7 @@ class ClaimList extends React.PureComponent {
initialNumToRender={3}
maxToRenderPerBatch={3}
removeClippedSubviews
renderItem={({ item }) => {
return item === Constants.MORE_PLACEHOLDER ? (
this.renderMorePlaceholder()
) : (
<FileItem
style={discoverStyle.fileItem}
mediaStyle={discoverStyle.fileItemMedia}
key={item}
uri={normalizeURI(item)}
navigation={navigation}
showDetails
compactView={false}
/>
);
}}
renderItem={this.renderHorizontalItem}
horizontal
showsHorizontalScrollIndicator={false}
data={uris ? this.appendMorePlaceholder(uris.slice(0, horizontalLimit)) : []}

View file

@ -0,0 +1,39 @@
import { connect } from 'react-redux';
import {
doResolveUri,
makeSelectClaimForUri,
makeSelectMetadataForUri,
makeSelectFileInfoForUri,
makeSelectIsUriResolving,
makeSelectClaimIsNsfw,
makeSelectShortUrlForUri,
makeSelectTitleForUri,
makeSelectThumbnailForUri,
} from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
import ClaimResultItem from './view';
const select = (state, props) => ({
blackListedOutpoints: selectBlackListedOutpoints(state),
claim: makeSelectClaimForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
filteredOutpoints: selectFilteredOutpoints(state),
isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
obscureNsfw: !selectShowNsfw(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state),
shortUrl: makeSelectShortUrlForUri(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
});
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(select, perform)(ClaimResultItem);

View file

@ -0,0 +1,180 @@
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri, getDownloadProgress, getStorageForFileInfo } from 'utils/helper';
import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DateTime from 'component/dateTime';
import FastImage from 'react-native-fast-image';
import FileItemMedia from 'component/fileItemMedia';
import FilePrice from 'component/filePrice';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import NsfwOverlay from 'component/nsfwOverlay';
import ProgressBar from 'component/progressBar';
import fileListStyle from 'styles/fileList';
import seedrandom from 'seedrandom';
class ClaimResultItem extends React.PureComponent {
state = {
autoStyle: null,
};
componentDidMount() {
const { result } = this.props;
if (!result || !result.name || !result.claimId) {
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
} else {
// result property set, use deterministic random style
const rng = seedrandom(normalizeURI(`${result.name}#${result.claimId}`));
const index = Math.floor(rng.quick() * ChannelIconItem.AUTO_THUMB_STYLES.length);
this.setState({ autoStyle: ChannelIconItem.AUTO_THUMB_STYLES[index] });
}
}
onPressHandler = () => {
const { autoplay, navigation, result, urlOpenHandler, setPlayerVisible } = this.props;
const { claimId, name } = result;
const url = normalizeURI(`${name}#${claimId}`);
if (urlOpenHandler) {
urlOpenHandler(url);
} else {
navigateToUri(navigation, url, { autoplay }, false, url, setPlayerVisible);
}
};
render() {
const { fileInfo, navigation, obscureNsfw, result, rewardedContentClaimIds, setPlayerVisible, style } = this.props;
const {
channel,
channel_claim_id: channelClaimId,
claimId,
duration,
fee,
name,
nsfw,
release_time: releaseTime,
thumbnail_url: thumbnailUrl,
title,
} = result;
const isChannel = name && name.startsWith('@');
const hasThumbnail = !!thumbnailUrl;
const obscure = obscureNsfw && nsfw;
const url = normalizeURI(`${name}#${claimId}`);
const hasChannel = !!channel;
const channelUrl = hasChannel ? normalizeURI(`${channel}#${channelClaimId}`) : null;
const isRewardContent = rewardedContentClaimIds.includes(claimId);
return (
<View style={style}>
<TouchableOpacity
style={[style, isChannel ? fileListStyle.channelContainer : null]}
onPress={this.onPressHandler}
>
{!isChannel && (
<FileItemMedia
style={fileListStyle.thumbnail}
duration={duration}
resizeMode="cover"
title={title || name || normalizeURI(url).substring(7)}
thumbnail={thumbnailUrl}
/>
)}
{isChannel && (
<View style={fileListStyle.channelThumbnailView}>
<View style={[fileListStyle.channelThumbnailContainer, this.state.autoStyle]}>
{hasThumbnail && (
<FastImage
style={fileListStyle.channelThumbnail}
resizeMode={FastImage.resizeMode.cover}
source={{ uri: thumbnailUrl }}
/>
)}
{!hasThumbnail && (
<Text style={channelIconStyle.autothumbCharacter}>
{title ? title.substring(0, 1).toUpperCase() : name.substring(1, 2).toUpperCase()}
</Text>
)}
</View>
</View>
)}
{fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon style={fileListStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
)}
<FilePrice
cost={fee ? parseFloat(fee) / 100000000 : 0}
uri={url}
style={fileListStyle.filePriceContainer}
iconStyle={fileListStyle.filePriceIcon}
textStyle={fileListStyle.filePriceText}
/>
<View style={fileListStyle.detailsContainer}>
{(title || name) && (
<View style={fileListStyle.titleContainer}>
<Text style={fileListStyle.title} numberOfLines={3}>
{title || name}
</Text>
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
</View>
)}
{(hasChannel || isChannel) && (
<Link
style={fileListStyle.publisher}
text={isChannel ? name : channel}
onPress={() => {
navigateToUri(
navigation,
normalizeURI(isChannel ? url : channelUrl),
null,
false,
isChannel ? url : channelUrl,
setPlayerVisible,
);
}}
/>
)}
<View style={fileListStyle.info}>
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text style={fileListStyle.infoText}>{getStorageForFileInfo(fileInfo)}</Text>
)}
<DateTime
style={fileListStyle.publishInfo}
textStyle={fileListStyle.infoText}
timeAgo
date={releaseTime}
/>
</View>
{fileInfo && fileInfo.download_path && (
<View style={fileListStyle.downloadInfo}>
{!fileInfo.completed && (
<ProgressBar
borderRadius={3}
color={Colors.NextLbryGreen}
height={3}
style={fileListStyle.progress}
progress={getDownloadProgress(fileInfo)}
/>
)}
</View>
)}
</View>
</TouchableOpacity>
{obscure && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
</View>
);
}
}
export default ClaimResultItem;

View file

@ -20,7 +20,7 @@ class CustomRewardCard extends React.PureComponent<Props> {
if (error && error.trim().length > 0) {
notify({ message: error });
} else {
notify({ message: 'Reward successfully claimed!' });
notify({ message: __('Reward successfully claimed!') });
this.setState({ rewardCode: '' });
}
this.setState({ claimStarted: false });
@ -37,12 +37,12 @@ class CustomRewardCard extends React.PureComponent<Props> {
if (showVerification) {
showVerification();
}
notify({ message: 'Unfortunately, you are not eligible to claim this reward at this time.' });
notify({ message: __('Unfortunately, you are not eligible to claim this reward at this time.') });
return;
}
if (!rewardCode || rewardCode.trim().length === 0) {
notify({ message: 'Please enter a reward code to claim.' });
notify({ message: __('Please enter a reward code to claim.') });
return;
}
@ -57,12 +57,12 @@ class CustomRewardCard extends React.PureComponent<Props> {
return (
<View style={[rewardStyle.rewardCard, rewardStyle.row]}>
<View style={rewardStyle.leftCol}>
{rewardIsPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
{rewardIsPending && <ActivityIndicator size="small" color={Colors.NextLbryGreen} />}
</View>
<View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>Custom Code</Text>
<Text style={rewardStyle.rewardTitle}>{__('Custom Code')}</Text>
<Text style={rewardStyle.rewardDescription}>
Are you a supermodel or rockstar that received a custom reward code? Claim it here.
{__('Are you a supermodel or rockstar that received a custom reward code? Claim it here.')}
</Text>
<View>
@ -74,7 +74,7 @@ class CustomRewardCard extends React.PureComponent<Props> {
/>
<Button
style={rewardStyle.redeemButton}
text={'Redeem'}
text={__('Redeem')}
disabled={!this.state.rewardCode || this.state.rewardCode.trim().length === 0 || rewardIsPending}
onPress={() => {
if (!rewardIsPending) {

View file

@ -1,4 +1,19 @@
import { connect } from 'react-redux';
import { doToast, selectBalance, selectMyChannelClaims } from 'lbry-redux';
import { selectUnclaimedRewardValue, selectUser } from 'lbryinc';
import { selectSdkReady } from 'redux/selectors/settings';
import DrawerContent from './view';
export default connect()(DrawerContent);
const select = state => ({
balance: selectBalance(state),
channels: selectMyChannelClaims(state),
sdkReady: selectSdkReady(state),
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
user: selectUser(state),
});
const perform = dispatch => ({
notify: data => dispatch(doToast(data)),
});
export default connect(select, perform)(DrawerContent);

View file

@ -1,36 +1,227 @@
import React from 'react';
import { DrawerItems, SafeAreaView } from 'react-navigation';
import { ScrollView } from 'react-native';
import Constants from 'constants';
import { Image, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import Button from 'component/button';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import channelIconStyle from 'styles/channelIcon';
import discoverStyle from 'styles/discover';
import { Lbryio } from 'lbryinc';
import { formatUsd } from 'utils/helper';
const groupedMenuItems = {
'Find content': [
{ icon: 'heart', solid: true, label: 'Following', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS },
{ icon: 'hashtag', label: 'Your Tags', route: Constants.DRAWER_ROUTE_DISCOVER },
{ icon: 'globe-americas', label: 'All Content', route: Constants.DRAWER_ROUTE_TRENDING },
],
'Your content': [
{ icon: 'at', label: 'Channels', route: Constants.DRAWER_ROUTE_CHANNEL_CREATOR },
{ icon: 'download', label: 'Library', route: Constants.DRAWER_ROUTE_MY_LBRY },
{ icon: 'cloud-upload-alt', label: 'Publishes', route: Constants.DRAWER_ROUTE_PUBLISHES },
{ icon: 'upload', label: 'New Publish', route: Constants.DRAWER_ROUTE_PUBLISH },
],
Wallet: [
{ icon: 'wallet', label: 'Wallet', route: Constants.DRAWER_ROUTE_WALLET },
{ icon: 'award', label: 'Rewards', route: Constants.DRAWER_ROUTE_REWARDS },
{ icon: 'user-friends', label: 'Invites', route: Constants.DRAWER_ROUTE_INVITES },
],
Settings: [
{ icon: 'cog', label: 'Settings', route: Constants.DRAWER_ROUTE_SETTINGS },
{ icon: 'info', label: 'About', route: Constants.DRAWER_ROUTE_ABOUT },
],
};
const groupNames = Object.keys(groupedMenuItems);
const routesRequiringSdkReady = [
Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
Constants.DRAWER_ROUTE_MY_LBRY,
Constants.DRAWER_ROUTE_PUBLISHES,
Constants.DRAWER_ROUTE_PUBLISH,
Constants.DRAWER_ROUTE_WALLET,
Constants.DRAWER_ROUTE_REWARDS,
Constants.DRAWER_ROUTE_INVITES,
];
class DrawerContent extends React.PureComponent {
state = {
usdExchangeRate: 0,
};
componentDidMount() {
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
}
getAvatarImageUrl = () => {
const { channels = [] } = this.props;
if (channels) {
// get the first channel thumbnail found. In the future, allow the user to select a default channel thumbnail?
for (let i = 0; i < channels.length; i++) {
if (channels[i].value && channels[i].value.thumbnail) {
return channels[i].value.thumbnail.url;
}
}
}
return null;
};
launchSignInFlow = () => {
// for now, sync flow (email, then password input) will be the default sign in flow
const { navigation } = this.props;
navigation.navigate({
routeName: 'Verification',
key: 'verification',
params: { syncFlow: true, signInFlow: true },
});
};
handleItemPress = routeName => {
const { navigation, notify, sdkReady } = this.props;
if (!sdkReady && routesRequiringSdkReady.includes(routeName)) {
if (navigation.closeDrawer) {
navigation.closeDrawer();
}
notify({
message: __('The background service is still initializing. Please try again when initialization is complete.'),
});
return;
}
navigation.navigate({ routeName: routeName });
};
render() {
const props = this.props;
const { navigation, onItemPress } = props;
const { activeTintColor, balance, navigation, unclaimedRewardAmount, user, onItemPress } = this.props;
const { state } = navigation;
const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
const signedIn = user && user.has_verified_email;
const avatarImageUrl = this.getAvatarImageUrl();
return (
<ScrollView>
<SafeAreaView style={discoverStyle.drawerContentContainer} forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems
{...props}
onItemPress={route => {
const { routeName } = route.route;
if (Constants.FULL_ROUTE_NAME_DISCOVER === routeName) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_DISCOVER });
return;
}
<View style={discoverStyle.drawerContentArea}>
{false && (
<View style={discoverStyle.signInContainer}>
{!signedIn && (
<Button
style={discoverStyle.signInButton}
theme={'light'}
text={__('Sign in')}
onPress={this.launchSignInFlow}
/>
)}
{signedIn && (
<View style={discoverStyle.signedIn}>
<View style={discoverStyle.signedInAvatar}>
{avatarImageUrl && (
<Image
style={discoverStyle.signedInAvatarImage}
resizeMode={'cover'}
source={{ uri: avatarImageUrl }}
/>
)}
{!avatarImageUrl && (
<Text style={channelIconStyle.autothumbCharacter}>
{user.primary_email.substring(0, 1).toUpperCase()}
</Text>
)}
</View>
<Text style={discoverStyle.signedInEmail} numberOfLines={1}>
{user.primary_email}
</Text>
</View>
)}
</View>
)}
if (Constants.FULL_ROUTE_NAME_WALLET === routeName) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_WALLET });
return;
}
<ScrollView contentContainerStyle={discoverStyle.menuScrollContent}>
<SafeAreaView
style={discoverStyle.drawerContentContainer}
forceInset={{ top: 'always', horizontal: 'never' }}
>
{!signedIn && (
<TouchableOpacity
accessible
accessibilityLabel={__('Sign In')}
onPress={this.launchSignInFlow}
delayPressIn={0}
style={[discoverStyle.signInMenuItem, discoverStyle.signInMenuItemButton]}
>
<Text style={discoverStyle.signInMenuItemButtonText}>{__('SIGN IN')}</Text>
</TouchableOpacity>
)}
onItemPress(route);
}}
/>
</SafeAreaView>
</ScrollView>
{signedIn && (
<View style={[discoverStyle.signInMenuItem, discoverStyle.signInMenuItemBorder]}>
<Text style={discoverStyle.signInMenuItemText} numberOfLines={1}>
{user.primary_email.toUpperCase()}
</Text>
</View>
)}
{groupNames.map(groupName => {
const menuItems = groupedMenuItems[groupName];
return (
<View key={groupName} style={discoverStyle.menuGroup}>
{groupNames[3] !== groupName && (
<Text key={`${groupName}-title`} style={discoverStyle.menuGroupName}>
{__(groupName)}
</Text>
)}
{menuItems.map(item => {
const focused =
activeItemKey === item.route ||
(activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER &&
item.route === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) ||
(activeItemKey === Constants.FULL_ROUTE_NAME_WALLET &&
item.route === Constants.DRAWER_ROUTE_WALLET);
return (
<TouchableOpacity
accessible
accessibilityLabel={__(item.label)}
style={[
discoverStyle.menuItemTouchArea,
focused ? discoverStyle.menuItemTouchAreaFocused : null,
]}
key={item.label}
onPress={() => this.handleItemPress(item.route)}
delayPressIn={0}
>
<View style={discoverStyle.menuItemIcon}>
<Icon
name={item.icon}
size={16}
solid={item.solid}
color={focused ? activeTintColor : null}
/>
</View>
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
{__(item.label)}
{item.label === 'Wallet' && this.state.usdExchangeRate > 0 && (
<Text> ({formatUsd(parseFloat(balance) * parseFloat(this.state.usdExchangeRate))})</Text>
)}
{item.label === 'Rewards' && this.state.usdExchangeRate > 0 && (
<Text>
{' '}
({formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(this.state.usdExchangeRate))})
</Text>
)}
</Text>
</TouchableOpacity>
);
})}
</View>
);
})}
</SafeAreaView>
</ScrollView>
</View>
);
}
}

View file

@ -0,0 +1,4 @@
import { connect } from 'react-redux';
import EmptyStateView from './view';
export default connect()(EmptyStateView);

View file

@ -0,0 +1,26 @@
import React from 'react';
import { NativeModules, Text, View, Image, TouchableOpacity } from 'react-native';
import Button from '../button';
import emptyStateStyle from 'styles/emptyState';
class EmptyStateView extends React.PureComponent {
render() {
const { message, buttonText, inner, onButtonPress } = this.props;
return (
<View
style={[emptyStateStyle.container, inner ? emptyStateStyle.innerContainer : emptyStateStyle.outerContainer]}
>
<Image style={emptyStateStyle.image} resizeMode={'stretch'} source={require('../../assets/gerbil-happy.png')} />
<Text style={emptyStateStyle.message}>{message}</Text>
{buttonText && (
<View style={emptyStateStyle.buttonContainer}>
<Button style={emptyStateStyle.button} text={buttonText} onPress={onButtonPress} />
</View>
)}
</View>
);
}
}
export default EmptyStateView;

View file

@ -11,11 +11,6 @@ class FileDownloadButton extends React.PureComponent {
}
}
componentWillReceiveProps(nextProps) {
//this.checkAvailability(nextProps.uri);
//this.restartDownload(nextProps);
}
restartDownload(props) {
const { downloading, fileInfo, uri, restartDownload } = props;
@ -35,7 +30,6 @@ class FileDownloadButton extends React.PureComponent {
fileInfo,
downloading,
uri,
purchaseUri,
costInfo,
isPlayable,
isViewable,
@ -45,18 +39,27 @@ class FileDownloadButton extends React.PureComponent {
doPause,
style,
openFile,
onFileActionPress,
onButtonLayout,
} = this.props;
if ((fileInfo && !fileInfo.stopped) || loading || downloading) {
if (fileInfo && fileInfo.download_path && fileInfo.completed) {
return (
<TouchableOpacity
onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]}
onPress={openFile}
>
<Text style={fileDownloadButtonStyle.text}>{isViewable ? __('View') : __('Open')}</Text>
</TouchableOpacity>
);
} else if ((fileInfo && !fileInfo.stopped) || loading || downloading) {
const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0,
label = fileInfo ? progress.toFixed(0) + '% complete' : 'Connecting...';
label = fileInfo ? __('%progress%% complete', { progress: progress.toFixed(0) }) : __('Connecting...');
return (
<View style={[style, fileDownloadButtonStyle.container]}>
<View
style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }}
></View>
<View style={{ width: `${progress}%`, backgroundColor: '#ff0000', position: 'absolute', left: 0, top: 0 }} />
<Text style={fileDownloadButtonStyle.text}>{label}</Text>
</View>
);
@ -64,43 +67,19 @@ class FileDownloadButton extends React.PureComponent {
if (!costInfo) {
return (
<View style={[style, fileDownloadButtonStyle.container]}>
<Text style={fileDownloadButtonStyle.text}>Fetching cost info...</Text>
<Text style={fileDownloadButtonStyle.text}>{__('Fetching cost info...')}</Text>
</View>
);
}
return (
<Button
icon={isPlayable ? 'play' : null}
text={isPlayable ? 'Play' : isViewable ? 'View' : 'Download'}
text={isPlayable ? __('Play') : isViewable ? __('View') : __('Download')}
onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]}
onPress={() => {
if (NativeModules.Firebase) {
NativeModules.Firebase.track('purchase_uri', { uri: uri });
}
purchaseUri(uri, costInfo, !isPlayable);
if (NativeModules.UtilityModule) {
NativeModules.UtilityModule.checkDownloads();
}
if (isPlayable && onPlay) {
this.props.onPlay();
}
if (isViewable && onView) {
this.props.onView();
}
}}
onPress={onFileActionPress}
/>
);
} else if (fileInfo && fileInfo.download_path) {
return (
<TouchableOpacity
onLayout={onButtonLayout}
style={[style, fileDownloadButtonStyle.container]}
onPress={openFile}
>
<Text style={fileDownloadButtonStyle.text}>{isViewable ? 'View' : 'Open'}</Text>
</TouchableOpacity>
);
}
return null;

View file

@ -8,18 +8,23 @@ import {
makeSelectTitleForUri,
makeSelectIsUriResolving,
makeSelectClaimIsNsfw,
makeSelectShortUrlForUri,
} from 'lbry-redux';
import { selectRewardContentClaimIds } from 'lbryinc';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
import FileItem from './view';
const select = (state, props) => ({
blackListedOutpoints: selectBlackListedOutpoints(state),
claim: makeSelectClaimForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
filteredOutpoints: selectFilteredOutpoints(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
rewardedContentClaimIds: selectRewardContentClaimIds(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
obscureNsfw: !selectShowNsfw(state),
shortUrl: makeSelectShortUrlForUri(props.uri)(state),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
@ -27,9 +32,7 @@ const select = (state, props) => ({
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(
select,
perform
)(FileItem);
export default connect(select, perform)(FileItem);

View file

@ -30,39 +30,65 @@ class FileItem extends React.PureComponent {
}
navigateToFileUri = () => {
const { navigation, uri } = this.props;
const { claim, navigation, uri, shortUrl } = this.props;
const normalizedUri = normalizeURI(uri);
if (NativeModules.Firebase) {
NativeModules.Firebase.track('explore_click', { uri: normalizedUri });
NativeModules.Firebase.track('explore_click', { uri: normalizedUri, short_url: shortUrl });
}
navigateToUri(navigation, normalizedUri);
navigateToUri(navigation, shortUrl || uri, null, false, claim ? claim.permanent_url : null);
};
render() {
const {
blackListedOutpoints,
claim,
title,
thumbnail,
fileInfo,
filteredOutpoints,
metadata,
isResolvingUri,
rewardedContentClaimIds,
style,
mediaStyle,
navigation,
nsfw,
obscureNsfw,
showDetails,
compactView,
setPlayerVisible,
titleBeforeThumbnail,
} = this.props;
if (claim && claim.value_type === 'channel') {
// don't display channels in the lists on the Your tags page
return null;
}
let shouldHide = false;
if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = !blackListedOutpoints
? filteredOutpoints
: blackListedOutpoints.concat(filteredOutpoints);
shouldHide = outpointsToHide.some(
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
);
}
if (shouldHide) {
// don't display blacklisted or filtered outpoints on the Your tags page
return null;
}
const uri = normalizeURI(this.props.uri);
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const obscure = obscureNsfw && nsfw;
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
const signingChannel = claim ? claim.signing_channel : null;
const channelName = signingChannel ? signingChannel.name : null;
const channelClaimId = signingChannel ? signingChannel.claim_id : null;
const fullChannelUri = channelClaimId ? `${channelName}#${channelClaimId}` : channelName;
const shortChannelUri = signingChannel ? signingChannel.short_url : null;
const height = claim ? claim.height : null;
const duration = claim && claim.value && claim.value.video ? claim.value.video.duration : null;
return (
<View style={style}>
@ -73,19 +99,23 @@ class FileItem extends React.PureComponent {
</Text>
)}
<FileItemMedia
duration={duration}
title={title}
thumbnail={thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
isResolvingUri={isResolvingUri}
style={mediaStyle}
/>
{!compactView && fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon style={discoverStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
)}
{!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path) && (
<FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
<FilePrice
uri={uri}
style={discoverStyle.filePriceContainer}
iconStyle={discoverStyle.filePriceIcon}
textStyle={discoverStyle.filePriceText}
/>
)}
{!compactView && (
<View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
@ -102,17 +132,23 @@ class FileItem extends React.PureComponent {
style={discoverStyle.channelName}
text={channelName}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
navigateToUri(
navigation,
normalizeURI(shortChannelUri || fullChannelUri),
null,
false,
fullChannelUri,
setPlayerVisible,
);
}}
/>
)}
{!channelName && !isResolvingUri && <Text style={discoverStyle.anonChannelName}>{__('Anonymous')}</Text>}
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
</View>
)}
</TouchableOpacity>
{obscureNsfw && (
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
)}
{obscure && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
</View>
);
}

View file

@ -2,24 +2,26 @@ import React from 'react';
import { ActivityIndicator, Image, Text, View } from 'react-native';
import Colors from 'styles/colors';
import FastImage from 'react-native-fast-image';
import VideoDuration from 'component/videoDuration';
import autothumbStyle from 'styles/autothumb';
import fileItemMediaStyle from 'styles/fileItemMedia';
class FileItemMedia extends React.PureComponent {
static AUTO_THUMB_STYLES = [
fileItemMediaStyle.autothumbPurple,
fileItemMediaStyle.autothumbRed,
fileItemMediaStyle.autothumbPink,
fileItemMediaStyle.autothumbIndigo,
fileItemMediaStyle.autothumbBlue,
fileItemMediaStyle.autothumbLightBlue,
fileItemMediaStyle.autothumbCyan,
fileItemMediaStyle.autothumbTeal,
fileItemMediaStyle.autothumbGreen,
fileItemMediaStyle.autothumbYellow,
fileItemMediaStyle.autothumbOrange,
autothumbStyle.autothumbPurple,
autothumbStyle.autothumbRed,
autothumbStyle.autothumbPink,
autothumbStyle.autothumbIndigo,
autothumbStyle.autothumbBlue,
autothumbStyle.autothumbLightBlue,
autothumbStyle.autothumbCyan,
autothumbStyle.autothumbTeal,
autothumbStyle.autothumbGreen,
autothumbStyle.autothumbYellow,
autothumbStyle.autothumbOrange,
];
state: {
state = {
imageLoadFailed: false,
};
@ -48,7 +50,7 @@ class FileItemMedia extends React.PureComponent {
return false;
}
if (thumbnail.substring(0, 7) != 'http://' && thumbnail.substring(0, 8) != 'https://') {
if (thumbnail.substring(0, 7) !== 'http://' && thumbnail.substring(0, 8) !== 'https://') {
return false;
}
@ -57,37 +59,36 @@ class FileItemMedia extends React.PureComponent {
render() {
let style = this.props.style;
const { blurRadius, isResolvingUri, thumbnail, title, resizeMode } = this.props;
const { duration, isResolvingUri, thumbnail, title, resizeMode } = this.props;
const atStyle = this.state.autoThumbStyle;
const hasDuration = !!duration;
if (this.isThumbnailValid(thumbnail) && !this.state.imageLoadFailed) {
if (style == null) {
style = fileItemMediaStyle.thumbnail;
}
if (blurRadius > 0) {
// No blur radius support in FastImage yet
return (
<Image
source={{ uri: thumbnail }}
blurRadius={blurRadius}
resizeMode={resizeMode ? resizeMode : 'cover'}
style={style}
/>
);
}
return (
<FastImage
source={{ uri: thumbnail }}
onError={() => this.setState({ imageLoadFailed: true })}
resizeMode={this.getFastImageResizeMode(resizeMode)}
style={style}
/>
<View style={style}>
<FastImage
source={{ uri: thumbnail }}
onError={() => this.setState({ imageLoadFailed: true })}
resizeMode={this.getFastImageResizeMode(resizeMode)}
style={fileItemMediaStyle.image}
/>
{duration > 0 && (
<VideoDuration
duration={duration}
style={fileItemMediaStyle.duration}
textStyle={fileItemMediaStyle.durationText}
/>
)}
</View>
);
}
return (
<View style={[style ? style : fileItemMediaStyle.autothumb, atStyle]}>
<View style={[style || fileItemMediaStyle.autothumb, atStyle]}>
{isResolvingUri && (
<View style={fileItemMediaStyle.resolving}>
<ActivityIndicator color={Colors.White} size={'large'} />
@ -104,6 +105,13 @@ class FileItemMedia extends React.PureComponent {
.toUpperCase()}
</Text>
)}
{duration > 0 && (
<VideoDuration
duration={duration}
style={fileItemMediaStyle.duration}
textStyle={fileItemMediaStyle.durationText}
/>
)}
</View>
);
}

View file

@ -53,39 +53,39 @@ class FileList extends React.PureComponent<Props, State> {
dateNew: fileInfos =>
this.props.sortByHeight
? fileInfos.slice().sort((fileInfo1, fileInfo2) => {
if (fileInfo1.pending) {
return -1;
}
const height1 = this.props.claimsById[fileInfo1.claim_id]
? this.props.claimsById[fileInfo1.claim_id].height
: 0;
const height2 = this.props.claimsById[fileInfo2.claim_id]
? this.props.claimsById[fileInfo2.claim_id].height
: 0;
if (height1 > height2) {
return -1;
} else if (height1 < height2) {
return 1;
}
return 0;
})
if (fileInfo1.pending) {
return -1;
}
const height1 = this.props.claimsById[fileInfo1.claim_id]
? this.props.claimsById[fileInfo1.claim_id].height
: 0;
const height2 = this.props.claimsById[fileInfo2.claim_id]
? this.props.claimsById[fileInfo2.claim_id].height
: 0;
if (height1 > height2) {
return -1;
} else if (height1 < height2) {
return 1;
}
return 0;
})
: [...fileInfos].reverse(),
dateOld: fileInfos =>
this.props.sortByHeight
? fileInfos.slice().sort((fileInfo1, fileInfo2) => {
const height1 = this.props.claimsById[fileInfo1.claim_id]
? this.props.claimsById[fileInfo1.claim_id].height
: 999999;
const height2 = this.props.claimsById[fileInfo2.claim_id]
? this.props.claimsById[fileInfo2.claim_id].height
: 999999;
if (height1 < height2) {
return -1;
} else if (height1 > height2) {
return 1;
}
return 0;
})
const height1 = this.props.claimsById[fileInfo1.claim_id]
? this.props.claimsById[fileInfo1.claim_id].height
: 999999;
const height2 = this.props.claimsById[fileInfo2.claim_id]
? this.props.claimsById[fileInfo2.claim_id].height
: 999999;
if (height1 < height2) {
return -1;
} else if (height1 > height2) {
return 1;
}
return 0;
})
: fileInfos,
title: fileInfos =>
fileInfos.slice().sort((fileInfo1, fileInfo2) => {
@ -160,7 +160,7 @@ class FileList extends React.PureComponent<Props, State> {
// This is unfortunate
// https://github.com/lbryio/lbry/issues/1159
const name = claimName || claimNameDownloaded;
uriParams.contentName = name;
uriParams.claimName = name;
uriParams.claimId = claimId;
const uri = buildURI(uriParams);
@ -179,7 +179,7 @@ class FileList extends React.PureComponent<Props, State> {
style={fileListStyle.fileItem}
uri={item}
navigation={navigation}
showDetails={true}
showDetails
compactView={false}
/>
)}

View file

@ -5,28 +5,35 @@ import {
makeSelectMetadataForUri,
makeSelectFileInfoForUri,
makeSelectIsUriResolving,
makeSelectClaimIsNsfw,
makeSelectShortUrlForUri,
makeSelectTitleForUri,
makeSelectThumbnailForUri,
} from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
import FileListItem from './view';
const select = (state, props) => ({
blackListedOutpoints: selectBlackListedOutpoints(state),
claim: makeSelectClaimForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
filteredOutpoints: selectFilteredOutpoints(state),
isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
obscureNsfw: !selectShowNsfw(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state),
shortUrl: makeSelectShortUrlForUri(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
});
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(
select,
perform
)(FileListItem);
export default connect(select, perform)(FileListItem);

View file

@ -1,10 +1,15 @@
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri, formatBytes } from 'utils/helper';
import { navigateToUri, getDownloadProgress, getStorageForFileInfo } from 'utils/helper';
import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import DateTime from 'component/dateTime';
import FastImage from 'react-native-fast-image';
import FileItemMedia from 'component/fileItemMedia';
import FilePrice from 'component/filePrice';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import NsfwOverlay from 'component/nsfwOverlay';
@ -12,22 +17,9 @@ import ProgressBar from 'component/progressBar';
import fileListStyle from 'styles/fileList';
class FileListItem extends React.PureComponent {
getStorageForFileInfo = fileInfo => {
if (!fileInfo.completed) {
const written = formatBytes(fileInfo.written_bytes);
const total = formatBytes(fileInfo.total_bytes);
return `(${written} / ${total})`;
}
return formatBytes(fileInfo.written_bytes);
};
formatTitle = title => {
if (!title) {
return title;
}
return title.length > 80 ? title.substring(0, 77).trim() + '...' : title;
state = {
autoStyle: null,
url: null,
};
getDownloadProgress = fileInfo => {
@ -35,64 +27,204 @@ class FileListItem extends React.PureComponent {
};
componentDidMount() {
const { claim, resolveUri, uri } = this.props;
if (!claim) {
const { claim, resolveUri, uri, batchResolve } = this.props;
if (!claim && !batchResolve) {
resolveUri(uri);
}
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
}
componentDidUpdate() {
const { claim, resolveUri, uri, batchResolve } = this.props;
if (!claim && uri !== this.state.url && !batchResolve) {
this.setState({ url: uri }, () => resolveUri(uri));
}
}
defaultOnPress = () => {
const { navigation, uri } = this.props;
navigateToUri(navigation, uri);
const { autoplay, claim, featuredResult, navigation, uri, shortUrl } = this.props;
if (featuredResult && !claim) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH, params: { vanityUrl: uri.trim() } });
} else {
navigateToUri(navigation, shortUrl || uri, { autoplay }, false, claim ? claim.permanent_url : null);
}
};
onPressHandler = () => {
const { claim, onPress } = this.props;
if (onPress) {
onPress(claim);
} else {
this.defaultOnPress();
}
};
render() {
const {
blackListedOutpoints,
claim,
fileInfo,
filteredOutpoints,
metadata,
nsfw,
featuredResult,
isResolvingUri,
isDownloaded,
style,
obscureNsfw,
onPress,
navigation,
rewardedContentClaimIds,
setPlayerVisible,
thumbnail,
hideChannel,
onLongPress,
selected,
title,
} = this.props;
const uri = normalizeURI(this.props.uri);
const obscureNsfw = this.props.obscureNsfw && metadata && metadata.nsfw;
const obscure = obscureNsfw && nsfw;
const isResolving = !fileInfo && isResolvingUri;
const duration = claim && claim.value && claim.value.video ? claim.value.video.duration : null;
let name, channel, height, channelClaimId, fullChannelUri, signingChannel;
let name,
channel,
height,
isRewardContent,
channelClaimId,
fullChannelUri,
repostUrl,
repostChannel,
repostChannelUrl,
shortChannelUri,
shouldHide,
signingChannel,
isRepost;
if (claim) {
name = claim.name;
signingChannel = claim.signing_channel;
channel = signingChannel ? signingChannel.name : null;
height = claim.height;
isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
channelClaimId = signingChannel ? signingChannel.claim_id : null;
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
shortChannelUri = signingChannel ? signingChannel.short_url : null;
repostUrl = claim.repost_url;
repostChannelUrl = claim.repost_channel_url;
if (repostUrl) {
const { claimName: repostName, channelName } = parseURI(repostUrl);
repostChannel = channelName;
}
isRepost = !!repostUrl;
if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = !blackListedOutpoints
? filteredOutpoints
: blackListedOutpoints.concat(filteredOutpoints);
shouldHide = outpointsToHide.some(
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
);
}
// TODO: hide channels on tag pages?
// shouldHide = 'channel' === claim.value_type;
}
if (featuredResult && !isResolvingUri && !claim && !title && !name) {
if (shouldHide || (!isResolvingUri && !claim && !featuredResult)) {
return null;
}
const actualHideChannel = !isRepost && hideChannel;
const isChannel = name && name.startsWith('@');
const hasThumbnail = !!thumbnail;
return (
<View style={style}>
<TouchableOpacity style={style} onPress={onPress || this.defaultOnPress}>
<FileItemMedia
style={fileListStyle.thumbnail}
blurRadius={obscureNsfw ? 15 : 0}
resizeMode="cover"
title={title || name}
thumbnail={thumbnail}
/>
{fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon style={fileListStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
<View>
{isRepost && (
<View style={fileListStyle.repostInfo}>
<Icon name={'retweet'} size={14} style={fileListStyle.repostIcon} />
<Text style={fileListStyle.repostChannelName}>
<Link
text={`@${repostChannel}`}
onPress={() =>
navigateToUri(
navigation,
normalizeURI(repostChannelUrl || `@${repostChannel}`),
null,
false,
null,
false,
)
}
/>{' '}
reposted
</Text>
</View>
)}
<TouchableOpacity
style={[style, isChannel ? fileListStyle.channelContainer : null]}
onPress={this.onPressHandler}
onLongPress={() => {
if (onLongPress) {
onLongPress(claim);
}
}}
>
{!isChannel && (
<FileItemMedia
style={fileListStyle.thumbnail}
duration={duration}
resizeMode="cover"
title={title || name || normalizeURI(uri).substring(7)}
thumbnail={thumbnail}
/>
)}
{isChannel && (
<View style={fileListStyle.channelThumbnailView}>
<View style={[fileListStyle.channelThumbnailContainer, this.state.autoStyle]}>
{hasThumbnail && (
<FastImage
style={fileListStyle.channelThumbnail}
resizeMode={FastImage.resizeMode.cover}
source={{ uri: thumbnail }}
/>
)}
{!hasThumbnail && (
<Text style={channelIconStyle.autothumbCharacter}>
{title ? title.substring(0, 1).toUpperCase() : claim.name.substring(1, 2).toUpperCase()}
</Text>
)}
</View>
</View>
)}
{selected && (
<View style={fileListStyle.selectedOverlay}>
<Icon name={'check-circle'} solid color={Colors.NextLbryGreen} size={32} />
</View>
)}
{fileInfo && fileInfo.completed && fileInfo.download_path && (
<Icon
style={featuredResult ? fileListStyle.featuredDownloadedIcon : fileListStyle.downloadedIcon}
solid
color={Colors.NextLbryGreen}
name={'folder'}
size={16}
/>
)}
<FilePrice
uri={uri}
style={fileListStyle.filePriceContainer}
iconStyle={fileListStyle.filePriceIcon}
textStyle={fileListStyle.filePriceText}
/>
<View style={fileListStyle.detailsContainer}>
{featuredResult && (
<Text style={fileListStyle.featuredUri} numberOfLines={1}>
@ -105,30 +237,50 @@ class FileListItem extends React.PureComponent {
{!title && !name && <Text style={fileListStyle.uri}>{uri}</Text>}
{!title && !name && (
<View style={fileListStyle.row}>
<ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.LbryGreen} />
<ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.NextLbryGreen} />
</View>
)}
</View>
)}
{(title || name) && (
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
{this.formatTitle(title) || this.formatTitle(name)}
</Text>
<View style={fileListStyle.titleContainer}>
<Text
style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}
numberOfLines={actualHideChannel ? 4 : 3}
>
{title || name}
</Text>
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
</View>
)}
{channel && !hideChannel && (
{featuredResult && !isResolving && !claim && (
<View style={fileListStyle.titleContainer}>
<Text style={fileListStyle.featuredTitle}>{__('Nothing here. Publish something!')}</Text>
</View>
)}
{(channel || isChannel) && !actualHideChannel && (
<Link
style={fileListStyle.publisher}
text={channel}
text={isChannel ? name : channel}
onPress={() => {
navigateToUri(navigation, normalizeURI(fullChannelUri));
navigateToUri(
navigation,
normalizeURI(isChannel ? uri : shortChannelUri || fullChannelUri),
null,
false,
isChannel ? claim && claim.permanent_url : fullChannelUri,
setPlayerVisible,
);
}}
/>
)}
<View style={fileListStyle.info}>
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
<Text style={fileListStyle.infoText}>{this.getStorageForFileInfo(fileInfo)}</Text>
<Text style={fileListStyle.infoText}>{getStorageForFileInfo(fileInfo)}</Text>
)}
<DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} />
</View>
@ -141,16 +293,14 @@ class FileListItem extends React.PureComponent {
color={Colors.NextLbryGreen}
height={3}
style={fileListStyle.progress}
progress={this.getDownloadProgress(fileInfo)}
progress={getDownloadProgress(fileInfo)}
/>
)}
</View>
)}
</View>
</TouchableOpacity>
{obscureNsfw && (
<NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />
)}
{obscure && <NsfwOverlay onPress={() => navigation.navigate({ routeName: 'Settings', key: 'settingsPage' })} />}
</View>
);
}

View file

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Text, View } from 'react-native';
import { formatCredits, formatFullPrice } from 'lbry-redux';
import Icon from 'react-native-vector-icons/FontAwesome5';
class CreditAmount extends React.PureComponent {
static propTypes = {
@ -42,11 +43,16 @@ class CreditAmount extends React.PureComponent {
let amountText;
if (this.props.showFree && parseFloat(this.props.amount) === 0) {
amountText = 'FREE';
amountText = __('FREE');
} else {
if (this.props.label) {
const label =
typeof this.props.label === 'string' ? this.props.label : parseFloat(amount) == 1 ? 'credit' : 'credits';
typeof this.props.label === 'string'
? this.props.label
: parseFloat(amount) === 1
? __('credit')
: __('credits');
// TODO: handling singular / plural in other languages?
amountText = `${formattedAmount} ${label}`;
} else {
@ -57,25 +63,27 @@ class CreditAmount extends React.PureComponent {
}
}
/*{this.props.isEstimate ? (
<span
className="credit-amount__estimate"
title={__('This is an estimate and does not include data fees')}
>
*
</span>
) : null}*/
return <Text style={style}>{amountText}</Text>;
return (
<Text style={style} numberOfLines={1}>
{amountText}
</Text>
);
}
}
class FilePrice extends React.PureComponent {
componentWillMount() {
this.fetchCost(this.props);
const { cost } = this.props;
if (isNaN(parseFloat(cost))) {
this.fetchCost(this.props);
}
}
componentWillReceiveProps(nextProps) {
this.fetchCost(nextProps);
const { cost } = this.props;
if (isNaN(parseFloat(cost))) {
this.fetchCost(nextProps);
}
}
fetchCost(props) {
@ -87,30 +95,25 @@ class FilePrice extends React.PureComponent {
}
render() {
const { costInfo, look = 'indicator', showFullPrice = false, style, textStyle } = this.props;
const { cost, costInfo, look = 'indicator', showFullPrice = false, style, iconStyle, textStyle } = this.props;
const isEstimate = costInfo ? !costInfo.includesData : null;
if (!costInfo) {
return (
<View style={style}>
<Text style={textStyle}>???</Text>
</View>
);
const amount = cost ? parseFloat(cost) : costInfo ? parseFloat(costInfo.cost) : 0;
if (isNaN(amount) || amount === 0) {
return null;
}
return (
<View style={style}>
<Icon name="coins" size={9} style={iconStyle} />
<CreditAmount
style={textStyle}
label={false}
amount={parseFloat(costInfo.cost)}
amount={amount}
isEstimate={isEstimate}
showFree
showFullPrice={showFullPrice}
>
???
</CreditAmount>
/>
</View>
);
}

View file

@ -9,8 +9,8 @@ class FileRewardsDriver extends React.PureComponent<Props> {
return (
<TouchableOpacity style={filePageStyle.rewardDriverCard} onPress={() => navigation.navigate('Rewards')}>
<Icon name="award" size={16} style={filePageStyle.rewardIcon} />
<Text style={filePageStyle.rewardDriverText}>Earn some credits to access this content.</Text>
<Icon name="award" size={16} style={filePageStyle.rewardDriverIcon} />
<Text style={filePageStyle.rewardDriverText}>{__('Earn some credits to access this content.')}</Text>
</TouchableOpacity>
);
}

View file

@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectBalance } from 'lbry-redux';
import { selectUnclaimedRewardValue } from 'lbryinc';
import Constants from 'constants';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import FloatingWalletBalance from './view';
const select = state => ({
@ -11,7 +11,4 @@ const select = state => ({
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
});
export default connect(
select,
null
)(FloatingWalletBalance);
export default connect(select, null)(FloatingWalletBalance);

View file

@ -23,7 +23,7 @@ class FloatingWalletBalance extends React.PureComponent<Props> {
style={floatingButtonStyle.pendingContainer}
onPress={() => navigation && navigation.navigate({ routeName: 'Rewards' })}
>
<Icon name="award" size={18} style={floatingButtonStyle.rewardIcon} />
<Icon name="award" size={14} style={floatingButtonStyle.rewardIcon} />
<Text style={floatingButtonStyle.text}>{unclaimedRewardAmount}</Text>
</TouchableOpacity>
)}
@ -31,9 +31,10 @@ class FloatingWalletBalance extends React.PureComponent<Props> {
style={floatingButtonStyle.container}
onPress={() => navigation && navigation.navigate({ routeName: 'WalletStack' })}
>
<Icon name="coins" size={12} style={floatingButtonStyle.balanceIcon} />
{isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />}
{(!isNaN(balance) || balance === 0) && (
<Text style={floatingButtonStyle.text}>{formatCredits(parseFloat(balance), 2) + ' LBC'}</Text>
<Text style={floatingButtonStyle.text}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</TouchableOpacity>
</View>

View file

@ -36,7 +36,7 @@ class MediaPlayer extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
buffering: false,
buffering: true,
backgroundPlayEnabled: false,
autoPaused: false,
rate: 1,
@ -45,9 +45,9 @@ class MediaPlayer extends React.PureComponent {
resizeMode: 'contain',
duration: 0.0,
currentTime: 0.0,
paused: !props.autoPlay,
paused: true,
fullscreenMode: false,
areControlsVisible: true,
areControlsVisible: false,
controlsTimeout: -1,
seekerOffset: 0,
seekerPosition: 0,
@ -87,7 +87,9 @@ class MediaPlayer extends React.PureComponent {
}
onLoad = data => {
const { autoPlay } = this.props;
this.setState({
buffering: false,
duration: data.duration,
});
@ -100,13 +102,17 @@ class MediaPlayer extends React.PureComponent {
if (this.props.onMediaLoaded) {
this.props.onMediaLoaded();
}
if (autoPlay) {
this.setState({ paused: false });
}
};
onProgress = data => {
const { savePosition, claim } = this.props;
this.setState({ buffering: false, currentTime: data.currentTime });
if (data.currentTime > 0 && Math.floor(data.currentTime) % positionSaveInterval === 0) {
if (claim && data.currentTime > 0 && Math.floor(data.currentTime) % positionSaveInterval === 0) {
const { claim_id: claimId, txid, nout } = claim;
savePosition(claimId, `${txid}:${nout}`, data.currentTime);
}
@ -167,7 +173,7 @@ class MediaPlayer extends React.PureComponent {
togglePlay = () => {
this.showPlayerControls();
this.setState({ paused: !this.state.paused }, this.handlePausedState);
this.setState({ paused: !this.state.paused }, this.updateBackgroundMediaNotification);
};
handlePausedState = () => {
@ -188,7 +194,7 @@ class MediaPlayer extends React.PureComponent {
};
onEnd = () => {
this.setState({ paused: true });
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
if (this.props.onPlaybackFinished) {
this.props.onPlaybackFinished();
}
@ -327,6 +333,10 @@ class MediaPlayer extends React.PureComponent {
}
};
onFocusChanged = evt => {
this.setState({ paused: !(this.state.paused && evt.hasAudioFocus) }, this.updateBackgroundMediaNotification);
};
onBuffer = () => {
if (!this.state.paused) {
this.setState({ buffering: true }, () => this.manualHidePlayerControls());
@ -341,6 +351,12 @@ class MediaPlayer extends React.PureComponent {
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
};
handleSeek = time => {
const { currentTime } = this.state;
const newTime = currentTime + time;
this.seekTo(newTime);
};
updateBackgroundMediaNotification = () => {
this.handlePausedState();
const { backgroundPlayEnabled } = this.props;
@ -362,14 +378,26 @@ class MediaPlayer extends React.PureComponent {
<Icon name={'arrow-left'} size={18} style={mediaPlayerStyle.backButtonIcon} />
</TouchableOpacity>
<TouchableOpacity style={mediaPlayerStyle.playPauseButton} onPress={this.togglePlay}>
{this.state.paused && <Icon name="play" size={40} color="#ffffff" />}
{!this.state.paused && <Icon name="pause" size={40} color="#ffffff" />}
</TouchableOpacity>
<View style={mediaPlayerStyle.midControls}>
<TouchableOpacity style={mediaPlayerStyle.rewind10} onPress={() => this.handleSeek(-10)}>
<Icon name="undo" size={24} color={Colors.White} />
<Text style={[mediaPlayerStyle.midControlText, mediaPlayerStyle.leftMidControlText]}>10</Text>
</TouchableOpacity>
<TouchableOpacity style={mediaPlayerStyle.playPauseButton} onPress={this.togglePlay}>
{this.state.paused && <Icon name="play" size={40} color={Colors.White} />}
{!this.state.paused && <Icon name="pause" size={40} color={Colors.White} />}
</TouchableOpacity>
<TouchableOpacity style={mediaPlayerStyle.forward10} onPress={() => this.handleSeek(10)}>
<Icon name="redo" size={24} color={Colors.White} />
<Text style={[mediaPlayerStyle.midControlText, mediaPlayerStyle.rightMidControlText]}>10</Text>
</TouchableOpacity>
</View>
<TouchableOpacity style={mediaPlayerStyle.toggleFullscreenButton} onPress={this.toggleFullscreenMode}>
{this.state.fullscreenMode && <Icon name="compress" size={16} color="#ffffff" />}
{!this.state.fullscreenMode && <Icon name="expand" size={16} color="#ffffff" />}
{this.state.fullscreenMode && <Icon name="compress" size={16} color={Colors.White} />}
{!this.state.fullscreenMode && <Icon name="expand" size={16} color={Colors.White} />}
</TouchableOpacity>
<Text style={mediaPlayerStyle.elapsedDuration}>{this.formatTime(this.state.currentTime)}</Text>
@ -418,6 +446,13 @@ class MediaPlayer extends React.PureComponent {
: mediaPlayerStyle.containedTrackingControls,
];
const seekerCircleStyle = [this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle];
if (!this.state.seeking) {
seekerCircleStyle.push(
this.state.fullscreenMode ? mediaPlayerStyle.seekerCircleTopFs : mediaPlayerStyle.seekerCircleTop,
);
}
return (
<View style={styles} onLayout={onLayout}>
<Video
@ -433,6 +468,7 @@ class MediaPlayer extends React.PureComponent {
}}
resizeMode={this.state.resizeMode}
playInBackground={this.state.backgroundPlayEnabled}
playWhenInactive={this.state.backgroundPlayEnabled}
style={mediaPlayerStyle.player}
rate={this.state.rate}
volume={this.state.volume}
@ -443,6 +479,7 @@ class MediaPlayer extends React.PureComponent {
onEnd={this.onEnd}
onError={this.onError}
minLoadRetryCount={999}
onAudioFocusChanged={this.onFocusChanged}
/>
{this.state.firstPlay && thumbnail && (
@ -468,7 +505,7 @@ class MediaPlayer extends React.PureComponent {
{this.state.buffering && (
<View style={mediaPlayerStyle.loadingContainer}>
<ActivityIndicator color={Colors.LbryGreen} size="large" />
<ActivityIndicator color={Colors.NextLbryGreen} size="large" />
</View>
)}
@ -482,7 +519,7 @@ class MediaPlayer extends React.PureComponent {
]}
{...this.seekResponder.panHandlers}
>
<View style={this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle} />
<View style={seekerCircleStyle} />
</View>
<TouchableOpacity
style={[

View file

@ -46,7 +46,7 @@ export default class ModalPicker extends React.PureComponent {
onPress={() => onItemSelected(item)}
>
{item.icon && <Icon style={modalPickerStyle.itemIcon} name={item.icon} size={16} />}
<Text style={modalPickerStyle.itemLabel}>{item.label}</Text>
<Text style={modalPickerStyle.itemLabel}>{__(item.label)}</Text>
{selectedItem && selectedItem.name === item.name && (
<Icon style={modalPickerStyle.itemSelected} name={'check'} color={Colors.LbryGreen} size={16} />
)}

View file

@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import {
doClearRepostError,
doFetchChannelListMine,
doRepost,
doToast,
selectBalance,
selectMyChannelClaims,
selectRepostError,
selectRepostLoading,
} from 'lbry-redux';
import ModalRepostView from './view';
const select = state => ({
balance: selectBalance(state),
channels: selectMyChannelClaims(state),
reposting: selectRepostLoading(state),
error: selectRepostError(state),
});
const perform = dispatch => ({
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
notify: data => dispatch(doToast(data)),
repost: options => dispatch(doRepost(options)),
clearError: () => dispatch(doClearRepostError()),
});
export default connect(select, perform)(ModalRepostView);

View file

@ -0,0 +1,204 @@
import React from 'react';
import { ActivityIndicator, Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { formatCredits, creditsToString } from 'lbry-redux';
import modalStyle from 'styles/modal';
import modalRepostStyle from 'styles/modalRepost';
import ChannelSelector from 'component/channelSelector';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import { logPublish } from 'utils/helper';
export default class ModalRepostView extends React.PureComponent {
depositAmountInput;
state = {
channelName: null,
creditsInputFocused: false,
depositAmount: '0.01',
repostName: null,
repostStarted: false,
showAdvanced: false,
};
componentDidMount() {
const { claim, fetchChannelListMine } = this.props;
const { name } = claim;
fetchChannelListMine();
this.setState({ repostName: name });
this.checkChannelSelection(this.props);
}
componentWillReceiveProps(nextProps) {
this.checkChannelSelection(nextProps);
const { notify } = this.props;
const { reposting, error } = nextProps;
if (this.state.repostStarted && !reposting && error) {
this.setState({ repostStarted: false });
notify({ message: error, isError: true });
}
}
checkChannelSelection = props => {
const { channels = [] } = props;
if (!this.state.channelName && channels && channels.length > 0) {
const firstChannel = channels[0];
this.setState({ channelName: firstChannel.name });
}
};
handleChannelChange = channelName => {
const { channels = [] } = this.props;
if (channels && channels.length > 0) {
const filtered = channels.filter(c => c.name === channelName);
if (filtered.length > 0) {
const channel = filtered[0];
this.setState({ channelName });
}
}
};
handleRepost = () => {
const { claim, balance, notify, repost, onRepostSuccessful, channels = [], clearError } = this.props;
const { depositAmount, repostName, channelName } = this.state;
if (parseInt(depositAmount, 10) > balance) {
notify({
message: 'Insufficient credits',
isError: true,
});
return;
}
clearError();
const channel = channels.filter(ch => ch.name === channelName)[0];
this.setState({ repostStarted: true }, () => {
repost({
name: repostName,
bid: creditsToString(depositAmount),
channel_id: channel.claim_id,
claim_id: claim.claim_id,
}).then(repostClaim => {
logPublish(repostClaim);
this.setState({ repostStarted: false });
notify({ message: __('The content was successfully reposted!') });
if (onRepostSuccessful) onRepostSuccessful();
});
});
};
render() {
const { balance, channels, reposting, title, onCancelPress, onOverlayPress } = this.props;
const canRepost = !!this.state.channelName && !!this.state.repostName;
const channelsLoaded = channels && channels.length > 0;
const processing = this.state.repostStarted || reposting || !channelsLoaded;
return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={modalStyle.container} activeOpacity={1}>
<View
style={modalRepostStyle.container}
onLayout={() => {
if (this.tipAmountInput) {
this.tipAmountInput.focus();
}
}}
>
<Text style={modalRepostStyle.title} numberOfLines={1}>
{__('Repost %title%', { title })}
</Text>
<Text style={modalRepostStyle.infoText}>
{__('Repost your favorite content to help more people discover them!')}
</Text>
<Text style={modalRepostStyle.label}>{__('Channel to post on')}</Text>
<ChannelSelector
showAnonymous={false}
channelName={this.state.channelName}
onChannelChange={this.handleChannelChange}
/>
{this.state.showAdvanced && (
<View>
<Text style={modalRepostStyle.label}>{__('Name')}</Text>
<View style={modalRepostStyle.nameRow}>
<TextInput
editable={false}
value={this.state.channelName ? `lbry://${this.state.channelName}/` : ''}
style={modalRepostStyle.input}
/>
<TextInput
editable={canRepost}
value={this.state.repostName}
underlineColorAndroid={Colors.NextLbryGreen}
selectTextOnFocus
onChangeText={value => this.setState({ repostName: value })}
style={modalRepostStyle.input}
/>
</View>
<Text style={modalRepostStyle.label}>{__('Deposit')}</Text>
<View style={modalRepostStyle.row}>
<View style={modalRepostStyle.amountRow}>
<TextInput
editable={!this.state.repostStarted}
ref={ref => (this.depositAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
underlineColorAndroid={Colors.NextLbryGreen}
keyboardType={'numeric'}
onFocus={() => this.setState({ creditsInputFocused: true })}
onBlur={() => this.setState({ creditsInputFocused: false })}
placeholder={'0'}
value={this.state.depositAmount}
selectTextOnFocus
style={modalRepostStyle.depositAmountInput}
/>
<Text style={modalRepostStyle.currency}>LBC</Text>
<View style={modalRepostStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={modalRepostStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
</View>
</View>
)}
<View style={modalRepostStyle.buttonRow}>
{!processing && (
<Link
style={modalRepostStyle.cancelLink}
text={__('Cancel')}
onPress={() => {
if (onCancelPress) onCancelPress();
}}
/>
)}
{processing && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
<View style={modalRepostStyle.rightButtonRow}>
<Link
style={modalRepostStyle.advancedLink}
text={this.state.showAdvanced ? __('Hide advanced') : __('Show advanced')}
onPress={() => {
this.setState({ showAdvanced: !this.state.showAdvanced });
}}
/>
<Button
text={__('Repost')}
style={modalRepostStyle.button}
disabled={!canRepost || this.state.repostStarted || reposting}
onPress={this.handleRepost}
/>
</View>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}
}

View file

@ -0,0 +1,9 @@
import { connect } from 'react-redux';
import { selectFetchingClaimSearch } from 'lbry-redux';
import ModalSuggestedSubscriptions from './view';
const select = state => ({
loadingSuggested: selectFetchingClaimSearch(state),
});
export default connect(select)(ModalSuggestedSubscriptions);

View file

@ -0,0 +1,29 @@
import React from 'react';
import { ActivityIndicator, ScrollView, Text, TouchableOpacity, View } from 'react-native';
import modalStyle from 'styles/modal';
import subscriptionsStyle from 'styles/subscriptions';
import Button from 'component/button';
import Colors from 'styles/colors';
import SuggestedSubscriptionsGrid from 'component/suggestedSubscriptionsGrid';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
export default class ModalSuggestedSubcriptions extends React.PureComponent {
render() {
const { loadingSuggested, navigation, onDonePress, onOverlayPress } = this.props;
return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={[modalStyle.container, subscriptionsStyle.modalContainer]} activeOpacity={1}>
<SuggestedSubscriptionsGrid inModal navigation={navigation} />
<View style={modalStyle.wideButtons}>
<Button style={modalStyle.wideDoneButton} text={__('Done')} onPress={onDonePress} />
{loadingSuggested && (
<ActivityIndicator size="small" color={Colors.White} style={subscriptionsStyle.modalLoading} />
)}
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}
}

View file

@ -8,7 +8,8 @@ import Icon from 'react-native-vector-icons/FontAwesome5';
import Tag from 'component/tag';
import TagSearch from 'component/tagSearch';
import modalTagSelectorStyle from 'styles/modalTagSelector';
import { __ } from 'utils/helper';
const minimumTags = 2;
export default class ModalTagSelector extends React.PureComponent {
handleAddTag = tag => {
@ -23,6 +24,9 @@ export default class ModalTagSelector extends React.PureComponent {
}
this.props.doToggleTagFollow(tag);
if (window.persistor) {
window.persistor.flush();
}
NativeModules.Firebase.track('tag_follow', { tag });
};
@ -31,7 +35,16 @@ export default class ModalTagSelector extends React.PureComponent {
return;
}
const { followedTags, doToast } = this.props;
if (followedTags.length <= minimumTags) {
doToast({ message: __(`You can follow a minimum of ${minimumTags} tags.`) });
return;
}
this.props.doToggleTagFollow(tag);
if (window.persistor) {
window.persistor.flush();
}
NativeModules.Firebase.track('tag_unfollow', { tag });
};
@ -41,9 +54,9 @@ export default class ModalTagSelector extends React.PureComponent {
return (
<TouchableOpacity style={modalTagSelectorStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<View style={modalTagSelectorStyle.container}>
<TouchableOpacity style={modalTagSelectorStyle.container} activeOpacity={1}>
<View style={modalTagSelectorStyle.titleRow}>
<Text style={modalTagSelectorStyle.title}>Customize your tags</Text>
<Text style={modalTagSelectorStyle.title}>{__('Customize your tags')}</Text>
</View>
<View style={modalTagSelectorStyle.tagList}>
{tags &&
@ -59,9 +72,9 @@ export default class ModalTagSelector extends React.PureComponent {
</View>
<TagSearch handleAddTag={this.handleAddTag} selectedTags={tags} />
<View style={modalTagSelectorStyle.buttons}>
<Button style={modalTagSelectorStyle.doneButton} text={'Done'} onPress={onDonePress} />
<Button style={modalTagSelectorStyle.doneButton} text={__('Done')} onPress={onDonePress} />
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}

View file

@ -0,0 +1,17 @@
import { connect } from 'react-redux';
import { doSendTip, doToast, selectBalance } from 'lbry-redux';
import { selectSdkReady } from 'redux/selectors/settings';
import ModalTipView from './view';
const select = state => ({
balance: selectBalance(state),
sdkReady: selectSdkReady(state),
});
const perform = dispatch => ({
notify: data => dispatch(doToast(data)),
sendTip: (amount, claimId, isSupport, successCallback, errorCallback) =>
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
});
export default connect(select, perform)(ModalTipView);

View file

@ -0,0 +1,157 @@
import React from 'react';
import { ActivityIndicator, Alert, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { formatCredits } from 'lbry-redux';
import modalStyle from 'styles/modal';
import modalTipStyle from 'styles/modalTip';
import Button from 'component/button';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
export default class ModalTipView extends React.PureComponent {
tipAmountInput = null;
state = {
creditsInputFocused: false,
sendTipStarted: false,
tipAmount: null,
};
handleSendTip = () => {
const { claim, balance, notify, onSendTipFailed, onSendTipSuccessful, sdkReady, sendTip } = this.props;
const { tipAmount } = this.state;
if (!sdkReady) {
notify({
message: __(
'The background service is still initializing. You can still explore and watch content during the initialization process.',
),
});
return;
}
if (tipAmount > balance) {
notify({
message: 'Insufficient credits',
isError: true,
});
return;
}
const amount = parseInt(tipAmount, 10);
const message =
amount === 1
? __('Please confirm you want to tip %amount% credit', { amount })
: __('Please confirm you want to tip %amount% credits', { amount });
Alert.alert(
__('Send Tip'),
message,
[
{ text: __('No') },
{
text: __('Yes'),
onPress: () => {
this.setState({ sendTipStarted: true }, () =>
sendTip(
tipAmount,
claim.claim_id,
false,
() => {
// success
this.setState({ tipAmount: null, sendTipStarted: false });
if (onSendTipSuccessful) onSendTipSuccessful();
},
() => {
// error
if (onSendTipFailed) onSendTipFailed();
},
),
);
},
},
],
{ cancelable: true },
);
};
render() {
const { balance, channelName, contentName, onCancelPress, onOverlayPress } = this.props;
const canSendTip = this.state.tipAmount > 0;
return (
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={modalStyle.container} activeOpacity={1}>
<View
style={modalTipStyle.container}
onLayout={() => {
if (this.tipAmountInput) {
this.tipAmountInput.focus();
}
}}
>
<Text style={modalTipStyle.title} numberOfLines={1}>
{channelName ? __('Send a tip to %channel%', { channel: channelName }) : __('Send a tip')}
</Text>
<View style={modalTipStyle.row}>
<View style={modalTipStyle.amountRow}>
<TextInput
editable={!this.state.sendTipStarted}
ref={ref => (this.tipAmountInput = ref)}
onChangeText={value => this.setState({ tipAmount: value })}
underlineColorAndroid={Colors.NextLbryGreen}
keyboardType={'numeric'}
onFocus={() => this.setState({ creditsInputFocused: true })}
onBlur={() => this.setState({ creditsInputFocused: false })}
placeholder={'0'}
value={this.state.tipAmount}
selectTextOnFocus
style={modalTipStyle.tipAmountInput}
/>
<Text style={modalTipStyle.currency}>LBC</Text>
<View style={modalTipStyle.balance}>
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
{this.state.creditsInputFocused && (
<Text style={modalTipStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
)}
</View>
</View>
{this.state.sendTipStarted && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
</View>
<View style={modalTipStyle.info}>
<Text style={modalTipStyle.infoText}>
{__(
'This will appear as a tip for %content%, which will boost its ability to be discovered while active.',
{ content: contentName },
)}{' '}
<Link
style={modalTipStyle.learnMoreLink}
text={__('Learn more.')}
href={'https://lbry.com/faq/tipping'}
/>
</Text>
</View>
<View style={modalTipStyle.buttonRow}>
<Link
style={modalTipStyle.cancelTipLink}
text={__('Cancel')}
onPress={() => {
if (onCancelPress) onCancelPress();
}}
/>
<Button
text={__('Send')}
style={modalTipStyle.button}
disabled={!canSendTip || this.state.sendTipStarted}
onPress={this.handleSendTip}
/>
</View>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
}
}

View file

@ -5,9 +5,9 @@ import discoverStyle from '../../styles/discover';
class NsfwOverlay extends React.PureComponent {
render() {
return (
<TouchableOpacity style={discoverStyle.overlay} activeOpacity={0.95} onPress={this.props.onPress}>
<TouchableOpacity style={discoverStyle.overlay} activeOpacity={1} onPress={this.props.onPress}>
<Text style={discoverStyle.overlayText}>
This content is Not Safe For Work. To view adult content, please change your Settings.
{__('This content is Not Safe For Work. To view adult content, please change your Settings.')}
</Text>
</TouchableOpacity>
);

View file

@ -4,17 +4,21 @@ import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import publishStyle from 'styles/publish';
class PublishRewadsDriver extends React.PureComponent<Props> {
class PublishRewardsDriver extends React.PureComponent<Props> {
render() {
const { navigation } = this.props;
return (
<TouchableOpacity style={publishStyle.rewardDriverCard} onPress={() => navigation.navigate('Rewards')}>
<Icon name="award" size={16} style={publishStyle.rewardIcon} />
<Text style={publishStyle.rewardDriverText}>Earn some credits to be able to publish your content.</Text>
<Text style={publishStyle.rewardDriverText}>
{__('Publishing requires credits.')}
{'\n'}
{__('Tap here to get some for free.')}
</Text>
</TouchableOpacity>
);
}
}
export default PublishRewadsDriver;
export default PublishRewardsDriver;

View file

@ -1,25 +1,35 @@
import { connect } from 'react-redux';
import {
doResolveUris,
doResolvedSearch,
makeSelectClaimForUri,
doSearch,
makeSelectRecommendedContentForUri,
makeSelectTitleForUri,
makeSelectResolvedRecommendedContentForUri,
selectResolvingUris,
selectIsSearching,
} from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings';
import RelatedContent from './view';
const RESULT_SIZE = 16;
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
recommendedContent: makeSelectRecommendedContentForUri(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
isSearching: selectIsSearching(state),
recommendedContent: makeSelectResolvedRecommendedContentForUri(
props.uri,
RESULT_SIZE,
props.claimId,
props.claimName,
props.title,
)(state),
resolvingUris: selectResolvingUris(state),
showNsfwContent: selectShowNsfw(state),
});
const perform = dispatch => ({
search: query => dispatch(doSearch(query, 20, undefined, true)),
resolveUris: uris => dispatch(doResolveUris(uris)),
searchRecommended: (query, claimId, nsfw) =>
dispatch(doResolvedSearch(query, RESULT_SIZE, undefined, true, { related_to: claimId }, nsfw)),
});
export default connect(
select,
perform
)(RelatedContent);
export default connect(select, perform)(RelatedContent);

View file

@ -1,66 +1,45 @@
import React from 'react';
import { ActivityIndicator, FlatList, Text, View } from 'react-native';
import { normalizeURI } from 'lbry-redux';
import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors';
import FileListItem from 'component/fileListItem';
import ClaimResultItem from 'component/claimResultItem';
import fileListStyle from 'styles/fileList';
import relatedContentStyle from 'styles/relatedContent';
export default class RelatedContent extends React.PureComponent<Props> {
constructor() {
super();
this.didSearch = undefined;
}
export default class RelatedContent extends React.PureComponent {
componentDidMount() {
this.getRecommendedContent();
}
componentDidUpdate(prevProps: Props) {
const { claim, uri } = this.props;
if (uri !== prevProps.uri) {
this.didSearch = false;
}
if (claim && !this.didSearch) {
this.getRecommendedContent();
const { title, claimId, searchRecommended, showNsfwContent } = this.props;
if (title && claimId) {
searchRecommended(title, claimId, showNsfwContent);
}
}
getRecommendedContent() {
const { search, title } = this.props;
if (title) {
search(title);
this.didSearch = true;
}
shouldComponentUpdate(nextProps, nextState) {
const { isSearching, recommendedContent } = nextProps;
return isSearching || (!isSearching && recommendedContent && recommendedContent.length > 0);
}
didSearch: ?boolean;
render() {
const { recommendedContent, isSearching, navigation } = this.props;
if (!isSearching && (!recommendedContent || recommendedContent.length === 0)) {
return null;
}
const { isSearching, recommendedContent, navigation, urlOpenHandler, uri, fullUri } = this.props;
return (
<View style={relatedContentStyle.container}>
<Text style={relatedContentStyle.title}>Related Content</Text>
<Text style={relatedContentStyle.title}>{__('Related Content')}</Text>
{isSearching && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
{recommendedContent &&
recommendedContent.map(recommendedUri => (
<FileListItem
recommendedContent.map(result => (
<ClaimResultItem
style={fileListStyle.item}
key={recommendedUri}
uri={recommendedUri}
uri={result ? normalizeURI(`${result.name}#${result.claimId}`) : null}
urlOpenHandler={urlOpenHandler}
key={result.claimId}
result={result}
navigation={navigation}
onPress={() => navigateToUri(navigation, recommendedUri, { autoplay: true })}
autoplay
/>
))}
{isSearching && <ActivityIndicator size="small" color={Colors.LbryGreen} style={relatedContentStyle.loading} />}
</View>
);
}

View file

@ -1,10 +1,11 @@
// @flow
import React from 'react';
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
import Colors from '../../styles/colors';
import { formatUsd } from 'utils/helper';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from '../link';
import rewardStyle from '../../styles/reward';
import Link from 'component/link';
import rewardStyle from 'styles/reward';
type Props = {
canClaim: boolean,
@ -13,6 +14,7 @@ type Props = {
id: string,
reward_title: string,
reward_amount: number,
reward_range?: string,
transaction_id: string,
created_at: string,
reward_description: string,
@ -33,7 +35,7 @@ class RewardCard extends React.PureComponent<Props> {
notify({ message: errorMessage });
clearError(reward);
} else {
notify({ message: 'Reward successfully claimed!' });
notify({ message: __('Reward successfully claimed!') });
}
this.setState({ claimStarted: false });
}
@ -46,7 +48,7 @@ class RewardCard extends React.PureComponent<Props> {
if (showVerification) {
showVerification();
}
notify({ message: 'Unfortunately, you are not eligible to claim this reward at this time.' });
notify({ message: __('Unfortunately, you are not eligible to claim this reward at this time.') });
return;
}
@ -55,8 +57,23 @@ class RewardCard extends React.PureComponent<Props> {
});
};
getDisplayAmount = () => {
const { reward } = this.props;
if (reward) {
const claimed = !!reward.transaction_id;
if (!claimed && reward.reward_range && reward.reward_range.includes('-')) {
return reward.reward_range.split('-')[1];
} else if (reward.reward_amount > 0) {
return reward.reward_amount;
}
}
// unknown amount which normally shouldn't happen
return '?';
};
render() {
const { canClaim, isPending, onClaimPress, reward } = this.props;
const { canClaim, isPending, onClaimPress, reward, usdExchangeRate } = this.props;
const claimed = !!reward.transaction_id;
return (
@ -86,7 +103,7 @@ class RewardCard extends React.PureComponent<Props> {
)}
</TouchableOpacity>
)}
{isPending && <ActivityIndicator size="small" color={Colors.LbryGreen} />}
{isPending && <ActivityIndicator size="small" color={Colors.NextLbryGreen} />}
</View>
<View style={rewardStyle.midCol}>
<Text style={rewardStyle.rewardTitle}>{reward.reward_title}</Text>
@ -96,13 +113,21 @@ class RewardCard extends React.PureComponent<Props> {
style={rewardStyle.link}
href={`https://explorer.lbry.com/tx/${reward.transaction_id}`}
text={reward.transaction_id.substring(0, 7)}
error={'The transaction URL could not be opened'}
error={__('The transaction URL could not be opened')}
/>
)}
</View>
<View style={rewardStyle.rightCol}>
<Text style={rewardStyle.rewardAmount}>{reward.reward_amount}</Text>
{reward.reward_range && reward.reward_range.indexOf('-') > -1 && (
<Text style={rewardStyle.rightColHeader}>{__('up to')}</Text>
)}
<Text style={rewardStyle.rewardAmount}>{this.getDisplayAmount()}</Text>
<Text style={rewardStyle.rewardCurrency}>LBC</Text>
{usdExchangeRate > 0 && (
<Text style={rewardStyle.rewardUsd}>
&asymp;{formatUsd(parseFloat(this.getDisplayAmount()) * parseFloat(usdExchangeRate))}
</Text>
)}
</View>
</TouchableOpacity>
);

View file

@ -1,12 +1,13 @@
import React from 'react';
import { NativeModules, Text, TouchableOpacity, View } from 'react-native';
import { Linking, NativeModules, Text, TouchableOpacity, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Constants from 'constants';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Link from 'component/link';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import rewardStyle from 'styles/reward';
import { formatUsd } from '../../utils/helper';
class RewardEnrolment extends React.Component {
componentDidMount() {
@ -24,26 +25,47 @@ class RewardEnrolment extends React.Component {
navigation.navigate({ routeName: 'Verification', key: 'verification', params: { syncFlow: false } });
};
onLearnMorePressed = () => {
Linking.openURL('https://lbry.com/faq/earn-credits');
};
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
const { unclaimedRewardAmount, usdExchangeRate } = this.props;
return (
<View style={rewardStyle.enrollContainer} onPress>
<View style={rewardStyle.enrollContainer}>
<View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
<Text style={rewardStyle.summaryText}>
{unclaimedRewardAmount === 1 && __('%amount% available credit', { amount: unclaimedRewardAmount })}
{unclaimedRewardAmount !== 1 && __('%amount% available credits', { amount: unclaimedRewardAmount })}
</Text>
</View>
<View style={rewardStyle.onboarding}>
<Text style={rewardStyle.enrollDescText}>
LBRY credits allow you to purchase content, publish content, and influence the network. You can start
earning credits by watching videos on LBRY.
{__('LBRY credits allow you to publish or purchase content.')}
{'\n\n'}
{__('You can obtain free credits worth %amount% after you provide an email address.', {
amount: formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(usdExchangeRate)),
})}
{'\n\n'}
<Link style={rewardStyle.learnMoreLink} text={__('Learn more')} onPress={this.onLearnMorePressed} />.
</Text>
</View>
<View style={rewardStyle.buttonRow}>
<Link style={rewardStyle.notInterestedLink} text={'Not interested'} onPress={this.onNotInterestedPressed} />
<Button style={rewardStyle.enrollButton} theme={'light'} text={'Enroll'} onPress={this.onEnrollPressed} />
<Link
style={rewardStyle.notInterestedLink}
text={__('Not interested')}
onPress={this.onNotInterestedPressed}
/>
<Button
style={rewardStyle.enrollButton}
theme={'light'}
text={__('Get started')}
onPress={this.onEnrollPressed}
/>
</View>
</View>
);

View file

@ -1,20 +0,0 @@
import { connect } from 'react-redux';
import { doToast } from 'lbry-redux';
import { doRewardList, selectUnclaimedRewardValue, selectFetchingRewards, selectUser } from 'lbryinc';
import RewardSummary from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
user: selectUser(state),
});
const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
notify: data => dispatch(doToast(data)),
});
export default connect(
select,
perform
)(RewardSummary);

View file

@ -1,82 +0,0 @@
import React from 'react';
import { NativeModules, Text, TouchableOpacity, View } from 'react-native';
import AsyncStorage from '@react-native-community/async-storage';
import Button from 'component/button';
import Colors from 'styles/colors';
import Icon from 'react-native-vector-icons/FontAwesome5';
import rewardStyle from 'styles/reward';
class RewardSummary extends React.Component {
static itemKey = 'rewardSummaryDismissed';
state = {
actionsLeft: 0,
dismissed: false,
};
componentDidMount() {
this.props.fetchRewards();
AsyncStorage.getItem(RewardSummary.itemKey).then(isDismissed => {
if ('true' === isDismissed) {
this.setState({ dismissed: true });
}
const { user } = this.props;
let actionsLeft = 0;
if (!user || !user.has_verified_email) {
actionsLeft++;
}
if (!user || !user.is_identity_verified) {
actionsLeft++;
}
this.setState({ actionsLeft });
});
}
onDismissPressed = () => {
AsyncStorage.setItem(RewardSummary.itemKey, 'true');
this.setState({ dismissed: true });
this.props.notify({
message: 'You can always claim your rewards from the Rewards page.',
});
};
handleSummaryPressed = () => {
const { showVerification } = this.props;
if (showVerification) {
showVerification();
}
};
render() {
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
if (!user) {
return null;
}
if (
this.state.dismissed ||
(user && user.is_reward_approved) ||
this.state.actionsLeft === 0 ||
unclaimedRewardAmount === 0
) {
return null;
}
return (
<TouchableOpacity style={rewardStyle.summaryContainer} onPress={this.handleSummaryPressed}>
<View style={rewardStyle.summaryRow}>
<Icon name="award" size={36} color={Colors.White} />
<Text style={rewardStyle.summaryText}>{unclaimedRewardAmount} unclaimed credits</Text>
</View>
<Button style={rewardStyle.dismissButton} theme={'light'} text={'Dismiss'} onPress={this.onDismissPressed} />
</TouchableOpacity>
);
}
}
export default RewardSummary;

View file

@ -0,0 +1,4 @@
import { connect } from 'react-redux';
import SdkLoadingStatus from './view';
export default connect()(SdkLoadingStatus);

View file

@ -0,0 +1,15 @@
import { ActivityIndicator, Text, View } from 'react-native';
import React from 'react';
import discoverStyle from 'styles/discover';
import Colors from 'styles/colors';
export default class SdkLoadingStatus extends React.PureComponent {
render() {
return (
<View style={discoverStyle.sdkLoading}>
<ActivityIndicator color={Colors.White} size={'small'} />
<Text style={discoverStyle.sdkLoadingText}>{__('The LBRY background service is initializing...')}</Text>
</View>
);
}
}

View file

@ -1,19 +0,0 @@
import { connect } from 'react-redux';
import { NativeModules } from 'react-native';
import { doSearch, doUpdateSearchQuery } from 'lbry-redux';
import SearchInput from './view';
const perform = dispatch => ({
search: search => {
if (NativeModules.Firebase) {
NativeModules.Firebase.track('search', { query: search });
}
return dispatch(doSearch(search));
},
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query, false)),
});
export default connect(
null,
perform
)(SearchInput);

View file

@ -1,41 +0,0 @@
import React from 'react';
import { TextInput } from 'react-native';
class SearchInput extends React.PureComponent {
static INPUT_TIMEOUT = 500;
state = {
changeTextTimeout: -1,
};
handleChangeText = text => {
clearTimeout(this.state.changeTextTimeout);
if (!text || text.trim().length < 2) {
// only perform a search if 2 or more characters have been input
return;
}
const { search, updateSearchQuery } = this.props;
updateSearchQuery(text);
let timeout = setTimeout(() => {
search(text);
}, SearchInput.INPUT_TIMEOUT);
this.setState({ changeTextTimeout: timeout });
};
render() {
const { style, value } = this.props;
return (
<TextInput
style={style}
placeholder="Search"
underlineColorAndroid="transparent"
value={value}
onChangeText={text => this.handleChangeText(text)}
/>
);
}
}
export default SearchInput;

View file

@ -1,9 +1,9 @@
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import { ActivityIndicator, Platform, Switch, Text, TouchableOpacity, View } from 'react-native';
import { formatBytes } from '../../utils/helper';
import Colors from '../../styles/colors';
import storageStatsStyle from '../../styles/storageStats';
import { formatBytes } from 'utils/helper';
import Colors from 'styles/colors';
import storageStatsStyle from 'styles/storageStats';
class StorageStatsCard extends React.PureComponent {
state = {
@ -65,7 +65,8 @@ class StorageStatsCard extends React.PureComponent {
}
render() {
if (this.state.totalBytes == 0) {
const { fileInfos } = this.props;
if (fileInfos.length === 0 || this.state.totalBytes === 0) {
return null;
}
@ -74,10 +75,10 @@ class StorageStatsCard extends React.PureComponent {
<View style={[storageStatsStyle.row, storageStatsStyle.totalSizeContainer]}>
<View style={storageStatsStyle.summary}>
<Text style={storageStatsStyle.totalSize}>{formatBytes(this.state.totalBytes, 2)}</Text>
<Text style={storageStatsStyle.annotation}>used</Text>
<Text style={storageStatsStyle.annotation}>{__('used')}</Text>
</View>
<View style={[storageStatsStyle.row, storageStatsStyle.toggleStatsContainer]}>
<Text style={storageStatsStyle.statsText}>Stats</Text>
<Text style={storageStatsStyle.statsText}>{__('Stats')}</Text>
<Switch
style={storageStatsStyle.statsToggle}
value={this.state.showStats}
@ -97,28 +98,28 @@ class StorageStatsCard extends React.PureComponent {
{this.state.totalAudioBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.audioDistribution]} />
<Text style={storageStatsStyle.legendText}>Audio</Text>
<Text style={storageStatsStyle.legendText}>{__('Audio')}</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalAudioBytes, 2)}</Text>
</View>
)}
{this.state.totalImageBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.imageDistribution]} />
<Text style={storageStatsStyle.legendText}>Images</Text>
<Text style={storageStatsStyle.legendText}>{__('Images')}</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalImageBytes, 2)}</Text>
</View>
)}
{this.state.totalVideoBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.videoDistribution]} />
<Text style={storageStatsStyle.legendText}>Videos</Text>
<Text style={storageStatsStyle.legendText}>{__('Videos')}</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalVideoBytes, 2)}</Text>
</View>
)}
{this.state.totalOtherBytes > 0 && (
<View style={[storageStatsStyle.row, storageStatsStyle.legendItem]}>
<View style={[storageStatsStyle.legendBox, storageStatsStyle.otherDistribution]} />
<Text style={storageStatsStyle.legendText}>Other</Text>
<Text style={storageStatsStyle.legendText}>{__('Other')}</Text>
<Text style={storageStatsStyle.legendSize}>{formatBytes(this.state.totalOtherBytes, 2)}</Text>
</View>
)}

View file

@ -19,7 +19,7 @@ class SubscribeButton extends React.PureComponent {
const iconColor = isSubscribed ? null : Colors.Red;
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const subscriptionLabel = isSubscribed ? null : __('Subscribe');
const subscriptionLabel = isSubscribed ? null : __('Follow');
const { claimName } = parseURI(uri);
return (
@ -28,7 +28,7 @@ class SubscribeButton extends React.PureComponent {
theme={'light'}
icon={isSubscribed ? 'heart-broken' : 'heart'}
iconColor={iconColor}
solid={isSubscribed ? false : true}
solid={!isSubscribed}
text={hideText ? null : subscriptionLabel}
onPress={() => {
subscriptionHandler({

View file

@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { doChannelSubscribe, doChannelUnsubscribe, selectSubscriptions, makeSelectIsSubscribed } from 'lbryinc';
import { doToast } from 'lbry-redux';
import SubscribeButtonOverlay from './view';
const select = (state, props) => ({
subscriptions: selectSubscriptions(state),
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
});
export default connect(
select,
{
doChannelSubscribe,
doChannelUnsubscribe,
doToast,
},
)(SubscribeButtonOverlay);

View file

@ -0,0 +1,36 @@
import React from 'react';
import { normalizeURI, parseURI } from 'lbry-redux';
import { NativeModules, Text, View, TouchableOpacity } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Button from 'component/button';
import Colors from 'styles/colors';
class SubscribeButtonOverlay extends React.PureComponent {
handlePress = () => {
const { claim, isSubscribed, doChannelSubscribe, doChannelUnsubscribe, uri } = this.props;
if (!claim) {
return;
}
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const { name: claimName } = claim;
subscriptionHandler({
channelName: claimName,
uri: normalizeURI(uri),
});
};
render() {
const { uri, isSubscribed, style } = this.props;
let styles = style.length ? style : [style];
return (
<TouchableOpacity style={styles} opacity={0.7} onPress={this.handlePress}>
{isSubscribed && <Icon name={'heart'} size={20} solid color={Colors.Red} />}
{!isSubscribed && <Icon name={'heart'} size={20} color={Colors.Red} />}
</TouchableOpacity>
);
}
}
export default SubscribeButtonOverlay;

View file

@ -5,17 +5,27 @@ import Button from 'component/button';
import Colors from 'styles/colors';
class SubscribeNotificationButton extends React.PureComponent {
render() {
handlePress = () => {
const {
uri,
name,
doChannelSubscriptionEnableNotifications,
doChannelSubscriptionDisableNotifications,
doToast,
enabledChannelNotifications,
isSubscribed,
style,
} = this.props;
const shouldNotify = enabledChannelNotifications.indexOf(name) > -1;
if (shouldNotify) {
doChannelSubscriptionDisableNotifications(name);
doToast({ message: 'You will not receive notifications for new content.' });
} else {
doChannelSubscriptionEnableNotifications(name);
doToast({ message: 'You will receive all notifications for new content.' });
}
};
render() {
const { enabledChannelNotifications, name, uri, isSubscribed, style } = this.props;
if (!isSubscribed) {
return null;
@ -31,23 +41,14 @@ class SubscribeNotificationButton extends React.PureComponent {
}
const shouldNotify = enabledChannelNotifications.indexOf(name) > -1;
const { claimName } = parseURI(uri);
return (
<Button
style={styles}
theme={'light'}
icon={shouldNotify ? 'bell-slash' : 'bell'}
solid={true}
onPress={() => {
if (shouldNotify) {
doChannelSubscriptionDisableNotifications(name);
doToast({ message: 'You will not receive notifications for new content.' });
} else {
doChannelSubscriptionEnableNotifications(name);
doToast({ message: 'You will receive all notifications for new content.' });
}
}}
solid
onPress={this.handlePress}
/>
);
}

View file

@ -6,6 +6,7 @@ import {
makeSelectTitleForUri,
makeSelectIsUriResolving,
} from 'lbry-redux';
import { doChannelSubscribe, doChannelUnsubscribe, makeSelectIsSubscribed } from 'lbryinc';
import SuggestedSubscriptionItem from './view';
const select = (state, props) => ({
@ -13,13 +14,13 @@ const select = (state, props) => ({
title: makeSelectTitleForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
});
const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)),
subscribe: subscription => doChannelSubscribe(subscription),
unsubscribe: subscription => doChannelUnsubscribe(subscription),
});
export default connect(
select,
perform
)(SuggestedSubscriptionItem);
export default connect(select, perform)(SuggestedSubscriptionItem);

View file

@ -1,69 +1,99 @@
import React from 'react';
import { buildURI, normalizeURI } from 'lbry-redux';
import { ActivityIndicator, FlatList, Image, Text, View } from 'react-native';
import { ActivityIndicator, FlatList, Image, Text, TouchableOpacity, View } from 'react-native';
import { navigateToUri } from 'utils/helper';
import Colors from 'styles/colors';
import ChannelIconItem from 'component/channelIconItem';
import channelIconStyle from 'styles/channelIcon';
import discoverStyle from 'styles/discover';
import FileItem from 'component/fileItem';
import SubscribeButton from 'component/subscribeButton';
import SubscribeButtonOverlay from 'component/subscribeButtonOverlay';
import subscriptionsStyle from 'styles/subscriptions';
import Link from 'component/link';
import Tag from 'component/tag';
class SuggestedSubscriptionItem extends React.PureComponent {
state = {
autoStyle: null,
};
componentDidMount() {
const { claim, uri, resolveUri } = this.props;
if (!claim) {
resolveUri(uri);
}
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
}
render() {
const { claim, isResolvingUri, navigation, thumbnail, title, uri } = this.props;
let tags;
if (claim && claim.value) {
tags = claim.value.tags;
let shortUrl, tags;
if (claim) {
shortUrl = claim.short_url;
if (claim.value) {
tags = claim.value.tags;
}
}
const hasThumbnail = !!thumbnail;
if (isResolvingUri) {
return (
<View style={subscriptionsStyle.itemLoadingContainer}>
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
</View>
);
}
return (
<View style={subscriptionsStyle.suggestedItem}>
<View style={subscriptionsStyle.suggestedItemThumbnailContainer}>
<Image
style={subscriptionsStyle.suggestedItemThumbnail}
resizeMode={'cover'}
source={thumbnail ? { uri: thumbnail } : require('../../assets/default_avatar.jpg')}
/>
<TouchableOpacity style={subscriptionsStyle.suggestedItem}>
<View style={[subscriptionsStyle.suggestedItemThumbnailContainer, this.state.autoStyle]}>
{hasThumbnail && (
<Image style={subscriptionsStyle.suggestedItemThumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} />
)}
{!hasThumbnail && (
<Text style={channelIconStyle.autothumbCharacter}>
{title ? title.substring(0, 1).toUpperCase() : claim ? claim.name.substring(1, 2).toUpperCase() : ''}
</Text>
)}
</View>
<View style={subscriptionsStyle.suggestedItemDetails}>
<View style={subscriptionsStyle.suggestedItemInfo}>
{title && (
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={1}>
{title}
</Text>
)}
<Text style={subscriptionsStyle.suggestedItemName} numberOfLines={1}>
{claim && claim.name}
</Text>
{tags && (
<View style={subscriptionsStyle.suggestedItemTagList}>
{tags &&
tags
.slice(0, 3)
.map(tag => <Tag style={subscriptionsStyle.tag} key={tag} name={tag} navigation={navigation} />)}
</View>
)}
</View>
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={2}>
{title || claim.name}
</Text>
{tags && (
<View style={subscriptionsStyle.suggestedItemTagList}>
{tags &&
tags
.slice(0, 1)
.map(tag => (
<Tag
numberOfLines={1}
onPress={this.handleItemPress}
style={subscriptionsStyle.tag}
key={tag}
name={tag}
navigation={navigation}
truncate
/>
))}
</View>
)}
</View>
<SubscribeButton style={subscriptionsStyle.suggestedItemSubscribe} uri={normalizeURI(uri)} />
</View>
{claim && (
<SubscribeButtonOverlay
claim={claim}
style={subscriptionsStyle.suggestedItemSubscribeOverlay}
uri={normalizeURI(claim.permanent_url)}
/>
)}
</TouchableOpacity>
);
}
}

View file

@ -1,20 +1,19 @@
import { connect } from 'react-redux';
import { doClaimSearch, selectFetchingClaimSearch, selectLastClaimSearchUris, selectFollowedTags } from 'lbry-redux';
import { doClaimSearch, selectFetchingClaimSearch, selectClaimSearchByQuery, selectFollowedTags } from 'lbry-redux';
import { selectSuggestedChannels, selectIsFetchingSuggested } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
import SuggestedSubscriptions from './view';
const select = state => ({
followedTags: selectFollowedTags(state),
suggested: selectSuggestedChannels(state),
loading: selectIsFetchingSuggested(state) || selectFetchingClaimSearch(state),
claimSearchUris: selectLastClaimSearchUris(state),
claimSearchByQuery: selectClaimSearchByQuery(state),
showNsfwContent: selectShowNsfw(state),
});
const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(10, options)),
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(
select,
perform
)(SuggestedSubscriptions);
export default connect(select, perform)(SuggestedSubscriptions);

View file

@ -1,7 +1,7 @@
import React from 'react';
import { ActivityIndicator, FlatList, SectionList, Text, View } from 'react-native';
import { normalizeURI } from 'lbry-redux';
import { __, navigateToUri } from 'utils/helper';
import { ActivityIndicator, SectionList, Text, View } from 'react-native';
import { MATURE_TAGS, createNormalizedClaimSearchKey, normalizeURI } from 'lbry-redux';
import { navigateToUri } from 'utils/helper';
import SubscribeButton from 'component/subscribeButton';
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
import Colors from 'styles/colors';
@ -11,47 +11,60 @@ import Link from 'component/link';
import _ from 'lodash';
class SuggestedSubscriptions extends React.PureComponent {
state = {
options: {},
};
componentDidMount() {
const { claimSearch, followedTags } = this.props;
const { claimSearch, followedTags, showNsfwContent } = this.props;
const options = {
any_tags: _.shuffle(followedTags.map(tag => tag.name)).slice(0, 2),
any_tags: _.shuffle(followedTags.map(tag => tag.name)).slice(0, 3),
page: 1,
no_totals: true,
claim_type: 'channel',
};
if (!showNsfwContent) {
options.not_tags = MATURE_TAGS;
}
this.setState({ options });
claimSearch(options);
}
buildSections = () => {
const { suggested, claimSearchUris } = this.props;
const { suggested, claimSearchByQuery } = this.props;
const claimSearchKey = createNormalizedClaimSearchKey(this.state.options);
const claimSearchUris = claimSearchByQuery[claimSearchKey];
const suggestedUris = suggested ? suggested.map(suggested => suggested.uri) : [];
return [
{
title: __('You might like'),
data: suggestedUris,
title: __('Suggested channels'),
data: claimSearchUris ? claimSearchUris.filter(uri => !suggestedUris.includes(uri)) : [],
},
{
title: __('Tags you follow'),
data: claimSearchUris ? claimSearchUris.filter(uri => !suggestedUris.includes(uri)) : [],
title: __('You might also like'),
data: _.shuffle(suggestedUris),
},
];
};
render() {
const { suggested, loading, navigation } = this.props;
const { suggested, inModal, loading, navigation } = this.props;
if (loading) {
return (
<View style={subscriptionsStyle.centered}>
<ActivityIndicator size="large" color={Colors.LbryGreen} />
<ActivityIndicator size="large" color={Colors.NextLbryGreen} />
</View>
);
}
return (
<SectionList
style={subscriptionsStyle.scrollContainer}
contentContainerStyle={subscriptionsStyle.suggestedScrollPadding}
style={inModal ? subscriptionsStyle.modalScrollContainer : subscriptionsStyle.scrollContainer}
contentContainerStyle={
inModal ? subscriptionsStyle.modalSuggestedScrollContent : subscriptionsStyle.suggestedScrollContent
}
renderItem={({ item, index, section }) => (
<SuggestedSubscriptionItem key={item} uri={normalizeURI(item)} navigation={navigation} />
)}

View file

@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import {
doClaimSearch,
selectFetchingClaimSearch,
selectClaimSearchByQuery,
selectClaimSearchByQueryLastPageReached,
selectFollowedTags,
} from 'lbry-redux';
import { selectSubscriptions, selectSuggestedChannels, selectIsFetchingSuggested } from 'lbryinc';
import { selectShowNsfw } from 'redux/selectors/settings';
import SuggestedSubscriptionsGrid from './view';
const select = state => ({
followedTags: selectFollowedTags(state),
subscriptions: selectSubscriptions(state),
suggested: selectSuggestedChannels(state),
loading: selectIsFetchingSuggested(state) || selectFetchingClaimSearch(state),
claimSearchByQuery: selectClaimSearchByQuery(state),
lastPageReached: selectClaimSearchByQueryLastPageReached(state),
showNsfwContent: selectShowNsfw(state),
});
const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)),
});
export default connect(select, perform)(SuggestedSubscriptionsGrid);

View file

@ -0,0 +1,111 @@
import React from 'react';
import { ActivityIndicator, SectionList, Text, View } from 'react-native';
import { MATURE_TAGS, createNormalizedClaimSearchKey, normalizeURI } from 'lbry-redux';
import { navigateToUri } from 'utils/helper';
import { FlatGrid } from 'react-native-super-grid';
import SubscribeButton from 'component/subscribeButton';
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
import Colors from 'styles/colors';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import discoverStyle from 'styles/discover';
import subscriptionsStyle from 'styles/subscriptions';
import Link from 'component/link';
import _ from 'lodash';
const suggestedPageSize = 24;
const softLimit = 2400;
class SuggestedSubscriptionsGrid extends React.PureComponent {
state = {
currentPage: 1,
options: {},
// maintain a local state of subscriptions so that changes don't affect the search
subscriptionIds: [],
};
buildClaimSearchOptions() {
const { showNsfwContent } = this.props;
const { currentPage } = this.state;
const options = {
no_totals: true,
page: currentPage,
page_size: suggestedPageSize,
claim_type: 'channel',
order_by: [Constants.ORDER_BY_EFFECTIVE_AMOUNT],
};
if (!showNsfwContent) {
options.not_tags = MATURE_TAGS;
}
if (this.state.subscriptionIds.length > 0) {
options.not_channel_ids = this.state.subscriptionIds;
}
return options;
}
doClaimSearch() {
const { claimSearch } = this.props;
const options = this.buildClaimSearchOptions();
claimSearch(options);
}
handleVerticalEndReached = () => {
// fetch more content
const { claimSearchByQuery, lastPageReached } = this.props;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
const uris = claimSearchByQuery[claimSearchKey];
if (
lastPageReached[claimSearchKey] ||
(uris.length > 0 && uris.length < suggestedPageSize) || uris.length >= softLimit
) {
return;
}
this.setState({ currentPage: this.state.currentPage + 1 }, () => this.doClaimSearch());
};
componentDidMount() {
const { claimSearch, followedTags, showNsfwContent, subscriptions } = this.props;
if (subscriptions && subscriptions.length > 0) {
this.setState(
{
subscriptionIds: subscriptions.map(subscription => subscription.uri.split('#')[1]),
},
() => this.doClaimSearch(),
);
} else {
this.doClaimSearch();
}
}
render() {
const { claimSearchByQuery, inModal, navigation } = this.props;
const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options);
const claimSearchUris = claimSearchByQuery[claimSearchKey];
return (
<FlatGrid
initialNumToRender={24}
maxToRenderPerBatch={48}
removeClippedSubviews
itemDimension={120}
spacing={1}
items={claimSearchUris}
style={inModal ? subscriptionsStyle.modalScrollContainer : subscriptionsStyle.scrollContainer}
contentContainerStyle={
inModal ? subscriptionsStyle.modalSuggestedScrollContent : subscriptionsStyle.suggestedScrollContent
}
renderItem={({ item, index }) => (
<SuggestedSubscriptionItem key={item} uri={normalizeURI(item)} navigation={navigation} />
)}
onEndReached={this.handleVerticalEndReached}
onEndReachedThreshold={0.2}
/>
);
}
}
export default SuggestedSubscriptionsGrid;

View file

@ -1,20 +1,22 @@
import React from 'react';
import { Text, TouchableOpacity, View } from 'react-native';
import { MATURE_TAGS } from 'lbry-redux';
import { formatTagName } from 'utils/helper';
import tagStyle from 'styles/tag';
import Colors from 'styles/colors';
import Constants from 'constants';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import Icon from 'react-native-vector-icons/FontAwesome5';
export default class Tag extends React.PureComponent {
onPressDefault = () => {
const { name, navigation, type, onAddPress, onRemovePress } = this.props;
if ('add' === type) {
if (type === 'add') {
if (onAddPress) {
onAddPress(name);
}
return;
}
if ('remove' === type) {
if (type === 'remove') {
if (onRemovePress) {
onRemovePress(name);
}
@ -28,7 +30,7 @@ export default class Tag extends React.PureComponent {
};
render() {
const { name, onPress, style, type } = this.props;
const { name, numberOfLines, onPress, style, type, truncate } = this.props;
let styles = [];
if (style) {
@ -40,7 +42,7 @@ export default class Tag extends React.PureComponent {
}
styles.push({
backgroundColor: Colors.TagGreen,
backgroundColor: MATURE_TAGS.includes(name) ? Colors.TagGrape : Colors.TagGreen,
borderRadius: 8,
marginBottom: 4,
});
@ -48,7 +50,9 @@ export default class Tag extends React.PureComponent {
return (
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
<View style={tagStyle.content}>
<Text style={tagStyle.text}>{name}</Text>
<Text style={tagStyle.text} numberOfLines={numberOfLines}>
{truncate ? formatTagName(name) : name}
</Text>
{type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />}
</View>
</TouchableOpacity>

View file

@ -1,5 +1,6 @@
import React from 'react';
import { KeyboardAvoidingView, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { MATURE_TAGS } from 'lbry-redux';
import Tag from 'component/tag';
import tagStyle from 'styles/tag';
import Colors from 'styles/colors';
@ -66,13 +67,14 @@ export default class TagSearch extends React.PureComponent {
};
render() {
const { name, style, type, selectedTags = [] } = this.props;
const { editable, name, style, type, selectedTags = [], showNsfwTags } = this.props;
return (
<View>
<TextInput
editable={editable}
style={tagStyle.searchInput}
placeholder={'Search for more tags'}
placeholder={__('Search for more tags')}
underlineColorAndroid={Colors.NextLbryGreen}
value={this.state.tag}
numberOfLines={1}
@ -85,6 +87,22 @@ export default class TagSearch extends React.PureComponent {
))}
</View>
</KeyboardAvoidingView>
{showNsfwTags && (
<View style={tagStyle.nsfwTagsContainer}>
<Text style={tagStyle.nsfwTagsTitle}>{__('Mature tags')}</Text>
<View style={tagStyle.tagResultsList}>
{MATURE_TAGS.map(tag => (
<Tag
key={tag}
name={tag}
style={tagStyle.tag}
type="add"
onAddPress={name => this.onAddTagPress(name)}
/>
))}
</View>
</View>
)}
</View>
);
}

View file

@ -2,10 +2,10 @@
import React from 'react';
import { Text, View, Linking } from 'react-native';
import { buildURI, formatCredits } from 'lbry-redux';
import { navigateToUri } from '../../../utils/helper';
import Link from '../../link';
import { navigateToUri } from 'utils/helper';
import Link from 'component/link';
import moment from 'moment';
import transactionListStyle from '../../../styles/transactionList';
import transactionListStyle from 'styles/transactionList';
class TransactionListItem extends React.PureComponent {
capitalize(string: string) {
@ -19,11 +19,12 @@ class TransactionListItem extends React.PureComponent {
return (
<View style={transactionListStyle.listItem}>
<View style={[transactionListStyle.row, transactionListStyle.topRow]}>
<View style={transactionListStyle.col}>
<View style={[transactionListStyle.col, transactionListStyle.leftCol]}>
<Text style={transactionListStyle.text}>{this.capitalize(type)}</Text>
{name && claimId && (
<Link
style={transactionListStyle.link}
numberOfLines={1}
onPress={() => navigateToUri(navigation, buildURI({ claimName: name, claimId }))}
text={name}
/>
@ -37,19 +38,19 @@ class TransactionListItem extends React.PureComponent {
</View>
</View>
<View style={transactionListStyle.row}>
<View style={transactionListStyle.col}>
<View style={[transactionListStyle.col, transactionListStyle.leftCol]}>
<Link
style={transactionListStyle.smallLink}
text={txid.substring(0, 8)}
href={`https://explorer.lbry.com/tx/${txid}`}
error={'The transaction URL could not be opened'}
error={__('The transaction URL could not be opened')}
/>
</View>
<View style={transactionListStyle.col}>
{date ? (
<Text style={transactionListStyle.smallText}>{moment(date).format('MMM D')}</Text>
) : (
<Text style={transactionListStyle.smallText}>Pending</Text>
<Text style={transactionListStyle.smallText}>{__('Pending')}</Text>
)}
</View>
</View>

View file

@ -47,7 +47,7 @@ class TransactionList extends React.PureComponent {
return (
<View>
{!transactionList.length && (
<Text style={transactionListStyle.noTransactions}>{emptyMessage || 'No transactions to list.'}</Text>
<Text style={transactionListStyle.noTransactions}>{emptyMessage || __('No transactions to list.')}</Text>
)}
{!!transactionList.length && (

View file

@ -1,12 +1,12 @@
// @flow
import React from 'react';
//import BusyIndicator from 'component/common/busy-indicator';
// import BusyIndicator from 'component/common/busy-indicator';
import { Text, View } from 'react-native';
import Button from '../button';
import Link from '../link';
import TransactionList from '../transactionList';
import type { Transaction } from '../transactionList/view';
import walletStyle from '../../styles/wallet';
import Button from 'component/button';
import Link from 'component/link';
import TransactionList from 'component/transactionList';
import type { Transaction } from 'component/transactionList/view';
import walletStyle from 'styles/wallet';
type Props = {
fetchTransactions: () => void,
@ -26,15 +26,15 @@ class TransactionListRecent extends React.PureComponent<Props> {
return (
<View style={walletStyle.transactionsCard}>
<View style={[walletStyle.row, walletStyle.transactionsHeader]}>
<Text style={walletStyle.transactionsTitle}>Recent Transactions</Text>
<Link style={walletStyle.link} navigation={navigation} text={'View All'} href={'#TransactionHistory'} />
<Text style={walletStyle.transactionsTitle}>{__('Recent Transactions')}</Text>
<Link style={walletStyle.link} navigation={navigation} text={__('View All')} href={'#TransactionHistory'} />
</View>
{fetchingTransactions && <Text style={walletStyle.infoText}>Fetching transactions...</Text>}
{fetchingTransactions && <Text style={walletStyle.infoText}>{__('Fetching transactions...')}</Text>}
{!fetchingTransactions && (
<TransactionList
navigation={navigation}
transactions={transactions}
emptyMessage={"Looks like you don't have any recent transactions."}
transactions={transactions.slice(0, 5)}
emptyMessage={__("Looks like you don't have any recent transactions.")}
/>
)}
</View>

View file

@ -4,8 +4,11 @@ import {
selectSearchState as selectSearch,
selectSearchValue,
selectSearchSuggestions,
SETTINGS,
} from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer';
import { selectCurrentRoute } from 'redux/selectors/drawer';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import UriBar from './view';
const select = state => {
@ -16,14 +19,16 @@ const select = state => {
query: selectSearchValue(state),
currentRoute: selectCurrentRoute(state),
suggestions: selectSearchSuggestions(state),
showUriBarSuggestions: makeSelectClientSetting(SETTINGS.SHOW_URI_BAR_SUGGESTIONS)(state),
};
};
const perform = dispatch => ({
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
});
export default connect(
select,
perform
perform,
)(UriBar);

Some files were not shown because too many files have changed in this diff Show more