Compare commits
135 commits
input-focu
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
aa5dd878c0 | ||
|
47c6fb5889 | ||
|
89a9571ce1 | ||
|
689e30a5f0 | ||
|
33b4806c84 | ||
|
7e1794ce29 | ||
|
4c4d561f30 | ||
|
a5953dcef0 | ||
|
d33ed55bdf | ||
|
12d7fbd472 | ||
|
4645a8f412 | ||
|
fda9dbc606 | ||
|
f26f1fe215 | ||
|
dabba98c0c | ||
|
08c6cf1b7f | ||
|
195d155c70 | ||
|
30f66d23de | ||
|
3c5dfc9899 | ||
|
6392dbe975 | ||
|
485e62725c | ||
|
8a2b0b9bc1 | ||
|
e372f1ead4 | ||
|
9582b7ad7d | ||
|
f8de4cbc1a | ||
|
82508559b9 | ||
|
e1f4994990 | ||
|
19d3d8cd94 | ||
|
bcfb65ed54 | ||
|
0536f0e4bf | ||
|
0db2c7e628 | ||
|
f05d357fa7 | ||
|
1f649b9d38 | ||
|
55b292ab0e | ||
|
736de8e5d9 | ||
|
f06252d397 | ||
|
fa25789855 | ||
|
514e077fbb | ||
|
3d44bfa88b | ||
|
f81e2d7441 | ||
|
81bdd37576 | ||
|
76938d3349 | ||
|
456c787683 | ||
|
3a5edc461a | ||
|
3a8af2d7a0 | ||
|
601c589f69 | ||
|
f5ae1f34f7 | ||
|
4656be64a7 | ||
|
8e377d0e74 | ||
|
bf7f836465 | ||
|
017787ef25 | ||
|
d9ada93ff3 | ||
|
95a33b411b | ||
|
9bc5a2ecac | ||
|
4e6267d5a9 | ||
|
dc0b06ce55 | ||
|
82d15f0c7f | ||
|
9f32363a09 | ||
|
467272d5c8 | ||
|
3594b70113 | ||
|
5f008b3ac9 | ||
|
3f21c4e08c | ||
|
3717a8072b | ||
|
46bfbd242a | ||
|
0361b10575 | ||
|
b9dec86cae | ||
|
2465b5514f | ||
|
df3a9587c0 | ||
|
5df8b8c4aa | ||
|
156e310368 | ||
|
683b1f4805 | ||
|
e263ad7569 | ||
|
24d580ea13 | ||
|
bfc3b11035 | ||
|
6bf2f9460d | ||
|
504baeefaf | ||
|
6c55633751 | ||
|
3395fa7aa2 | ||
|
7a3def7ea1 | ||
|
f1aafd6e34 | ||
|
1ce5fad8c0 | ||
|
4690451bfe | ||
|
b149c0039a | ||
|
e97bf7ce79 | ||
|
8c79115ce0 | ||
|
24fa80d92a | ||
|
624d95ab42 | ||
|
8519b561b3 | ||
|
00dfc205a5 | ||
|
b6ab92a700 | ||
|
561aa0db9c | ||
|
ad9056f91a | ||
|
bc62580aae | ||
|
f5cb8e44d1 | ||
|
c47c7cd925 | ||
|
b25f913921 | ||
|
673d6dc0f5 | ||
|
91eb18ec9b | ||
|
4ed3809ffd | ||
|
3f0ed7988a | ||
|
b3e20e78cb | ||
|
331f3d58ee | ||
|
3b0ea191fe | ||
|
20a984f259 | ||
|
2d91c83628 | ||
|
d7635dc7ee | ||
|
13136e606d | ||
|
5ae5144eda | ||
|
3e81d4ef8d | ||
|
8365fa3fa0 | ||
|
5681630e4c | ||
|
1eecae469d | ||
|
f7c8bf3949 | ||
|
d538838921 | ||
|
b5abddb69a | ||
|
15cbdcfaca | ||
|
30246a189c | ||
|
f77f0a7aa2 | ||
|
36d4bd7e99 | ||
|
b8f2a9e24f | ||
|
d7edbf55b5 | ||
|
d362d9e8dd | ||
|
f47693fb35 | ||
|
54b10c818e | ||
|
4b283e6606 | ||
|
67723813cc | ||
|
80b12a2cc8 | ||
|
48ce7e9d9e | ||
|
696ff616f7 | ||
|
94050e5e6d | ||
|
d52436e8d4 | ||
|
f231741789 | ||
|
dd7f1e82ba | ||
|
17b381de71 | ||
|
53f143f232 | ||
|
c58aecb35e |
160 changed files with 15492 additions and 12290 deletions
|
@ -10,6 +10,7 @@
|
||||||
"__": true
|
"__": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"no-console": 2,
|
||||||
"no-multi-spaces": 0,
|
"no-multi-spaces": 0,
|
||||||
"new-cap": 0,
|
"new-cap": 0,
|
||||||
"prefer-promise-reject-errors": 0,
|
"prefer-promise-reject-errors": 0,
|
||||||
|
|
64
.gitignore
vendored
64
.gitignore
vendored
|
@ -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/
|
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
|
*.log
|
||||||
.vagrant
|
.vagrant
|
||||||
|
android/app/build/*
|
||||||
|
android/bin
|
||||||
|
|
8
.gitlab-ci.yml
Normal file
8
.gitlab-ci.yml
Normal 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
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "android"]
|
||||||
|
path = android
|
||||||
|
url = https://github.com/lbryio/lbry-android
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017-2019 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:
|
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:
|
||||||
|
|
||||||
|
|
39
README.md
39
README.md
|
@ -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)
|
[![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" />
|
<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.
|
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
|
## 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
|
## 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
|
## Contributing
|
||||||
Contributions to this project are welcome, encouraged, and compensated. For more details, see https://lbry.io/faq/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
14
__tests__/App-test.js
Normal 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
1
android
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ff30e7f6a4358fd997a9e6d9f75bfe6959eafcb6
|
2
bundle-android.sh
Normal file
2
bundle-android.sh
Normal 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/
|
4
index.js
4
index.js
|
@ -1,3 +1,7 @@
|
||||||
|
if (__DEV__) {
|
||||||
|
import('./reactotron').then(() => console.log('Reactotron Configured'));
|
||||||
|
}
|
||||||
|
|
||||||
import LBRYApp from './src/index';
|
import LBRYApp from './src/index';
|
||||||
|
|
||||||
export default LBRYApp;
|
export default LBRYApp;
|
||||||
|
|
53
ios/LbryApp-tvOS/Info.plist
Normal file
53
ios/LbryApp-tvOS/Info.plist
Normal 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>
|
24
ios/LbryApp-tvOSTests/Info.plist
Normal file
24
ios/LbryApp-tvOSTests/Info.plist
Normal 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>
|
782
ios/LbryApp.xcodeproj/project.pbxproj
Normal file
782
ios/LbryApp.xcodeproj/project.pbxproj
Normal 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 */;
|
||||||
|
}
|
|
@ -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>
|
129
ios/LbryApp.xcodeproj/xcshareddata/xcschemes/LbryApp.xcscheme
Normal file
129
ios/LbryApp.xcodeproj/xcshareddata/xcschemes/LbryApp.xcscheme
Normal 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
15
ios/LbryApp/AppDelegate.h
Normal 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
42
ios/LbryApp/AppDelegate.m
Normal 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
|
42
ios/LbryApp/Base.lproj/LaunchScreen.xib
Normal file
42
ios/LbryApp/Base.lproj/LaunchScreen.xib
Normal 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>
|
38
ios/LbryApp/Images.xcassets/AppIcon.appiconset/Contents.json
Normal file
38
ios/LbryApp/Images.xcassets/AppIcon.appiconset/Contents.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
6
ios/LbryApp/Images.xcassets/Contents.json
Normal file
6
ios/LbryApp/Images.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
57
ios/LbryApp/Info.plist
Normal file
57
ios/LbryApp/Info.plist
Normal 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
16
ios/LbryApp/main.m
Normal 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]));
|
||||||
|
}
|
||||||
|
}
|
24
ios/LbryAppTests/Info.plist
Normal file
24
ios/LbryAppTests/Info.plist
Normal 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>
|
72
ios/LbryAppTests/LbryAppTests.m
Normal file
72
ios/LbryAppTests/LbryAppTests.m
Normal 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
53
ios/Podfile
Normal 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
|
|
@ -1,64 +1,86 @@
|
||||||
{
|
{
|
||||||
"Waiting for name resolution": "Waiting for name resolution",
|
"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",
|
"Starting up": "Starting up",
|
||||||
"Connecting": "Connecting",
|
"Connecting": "Connecting",
|
||||||
"Unlocking account": "Unlocking account",
|
"Testing network": "Testing network",
|
||||||
"Decrypting wallet": "Decrypting wallet",
|
"Waiting for name resolution": "Waiting for name resolution",
|
||||||
|
"Search movies, music, and more": "Search movies, music, and more",
|
||||||
"Your Tags": "Your Tags",
|
"Your Tags": "Your Tags",
|
||||||
|
"Trending": "Trending",
|
||||||
"Customize": "Customize",
|
"Customize": "Customize",
|
||||||
|
"Sign In": "Sign In",
|
||||||
|
"SIGN IN": "SIGN IN",
|
||||||
"Find content": "Find content",
|
"Find content": "Find content",
|
||||||
|
"Subscriptions": "Subscriptions",
|
||||||
|
"All Content": "All Content",
|
||||||
"Your content": "Your content",
|
"Your content": "Your content",
|
||||||
|
"Channels": "Channels",
|
||||||
|
"Library": "Library",
|
||||||
|
"Publishes": "Publishes",
|
||||||
|
"New Publish": "New Publish",
|
||||||
"Wallet": "Wallet",
|
"Wallet": "Wallet",
|
||||||
|
"Rewards": "Rewards",
|
||||||
|
"Settings": "Settings",
|
||||||
|
"About": "About",
|
||||||
"All tags you follow": "All tags you follow",
|
"All tags you follow": "All tags you follow",
|
||||||
|
"More tags you follow": "More tags you follow",
|
||||||
"FREE": "FREE",
|
"FREE": "FREE",
|
||||||
"Delete": "Delete",
|
"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",
|
"Share": "Share",
|
||||||
"Tip": "Tip",
|
"Tip": "Tip",
|
||||||
"Report": "Report",
|
"Report": "Report",
|
||||||
"View": "View",
|
|
||||||
"Follow": "Follow",
|
|
||||||
"Fetching cost info...": "Fetching cost info...",
|
"Fetching cost info...": "Fetching cost info...",
|
||||||
"Play": "Play",
|
"Play": "Play",
|
||||||
"Channels you follow": "Channels you follow",
|
"Connecting...": "Connecting...",
|
||||||
"Suggested": "Suggested",
|
"Related Content": "Related Content",
|
||||||
"ALL": "ALL",
|
"Loading...": "Loading...",
|
||||||
"No content to display at this time. Please check back later.": "No content to display at this time. Please check back later.",
|
"Content Freedom": "Content Freedom",
|
||||||
"All Content": "All Content",
|
"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.",
|
||||||
"Create a channel": "Create a channel",
|
"What is LBRY?": "What is LBRY?",
|
||||||
"Title": "Title",
|
"Android App Basics": "Android App Basics",
|
||||||
"Channel": "Channel",
|
"Frequently Asked Questions": "Frequently Asked Questions",
|
||||||
"Deposit": "Deposit",
|
"Get Social": "Get Social",
|
||||||
"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.",
|
"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.",
|
||||||
"Show optional fields": "Show optional fields",
|
"App info": "App info",
|
||||||
"Cancel": "Cancel",
|
"App version": "App version",
|
||||||
"Create": "Create",
|
"LBRY SDK": "LBRY SDK",
|
||||||
"Description": "Description",
|
"Platform": "Platform",
|
||||||
"Website": "Website",
|
"Installation ID": "Installation ID",
|
||||||
"Email": "Email",
|
"Logs": "Logs",
|
||||||
"Tags": "Tags",
|
"Send log": "Send log",
|
||||||
"Hide optional fields": "Hide optional fields",
|
"Account Recommended": "Account Recommended",
|
||||||
"Mature tags": "Mature tags",
|
"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.",
|
||||||
"Other": "Other",
|
"Skip Account": "Skip Account",
|
||||||
"Publish something new": "Publish something new",
|
"Sign Up": "Sign Up",
|
||||||
"Record": "Record",
|
"Blockchain Sync": "Blockchain Sync",
|
||||||
"Take a photo": "Take a photo",
|
"Catching up with the blockchain (%progress%%)": "Catching up with the blockchain (%progress%%)",
|
||||||
"Upload a file": "Upload a file",
|
"Network Loading": "Network Loading",
|
||||||
"Please wait while we load your videos...": "Please wait while we load your videos...",
|
"Initializing LBRY service": "Initializing LBRY service",
|
||||||
"Price": "Price",
|
"%amount% available credits": "%amount% available credits",
|
||||||
"Your content will be free. Press the toggle to set a price.": "Your content will be free. Press the toggle to set a price.",
|
"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.",
|
||||||
"Content address": "Content address",
|
"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.",
|
||||||
"The address where people can find your content (ex. lbry://myvideo). ": "The address where people can find your content (ex. lbry://myvideo). ",
|
"Learn more": "Learn more",
|
||||||
"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.",
|
"Not interested": "Not interested",
|
||||||
"Show extra fields": "Show extra fields",
|
"Get started": "Get started",
|
||||||
"Publish": "Publish",
|
"Get %amount% free credits after creating an account.": "Get %amount% free credits after creating an account.",
|
||||||
"Additional Options": "Additional Options",
|
|
||||||
"Language": "Language",
|
|
||||||
"License": "License",
|
|
||||||
"None": "None",
|
|
||||||
"Public Domain": "Public Domain",
|
|
||||||
"Copyrighted...": "Copyrighted...",
|
|
||||||
"Other...": "Other...",
|
|
||||||
"Hide extra fields": "Hide extra fields",
|
|
||||||
"Balance": "Balance",
|
"Balance": "Balance",
|
||||||
"You currently have": "You currently have",
|
"You currently have": "You currently have",
|
||||||
"Receive Credits": "Receive Credits",
|
"Receive Credits": "Receive Credits",
|
||||||
|
@ -68,175 +90,237 @@
|
||||||
"Send Credits": "Send Credits",
|
"Send Credits": "Send Credits",
|
||||||
"Recipient address": "Recipient address",
|
"Recipient address": "Recipient address",
|
||||||
"Amount": "Amount",
|
"Amount": "Amount",
|
||||||
|
"Send": "Send",
|
||||||
|
"Recent Transactions": "Recent Transactions",
|
||||||
"View All": "View All",
|
"View All": "View All",
|
||||||
"Looks like you don't have any recent transactions.": "Looks like you don't have any recent transactions.",
|
"Looks like you don't have any recent transactions.": "Looks like you don't have any recent transactions.",
|
||||||
"Wallet Sync": "Wallet Sync",
|
"Wallet Sync": "Wallet Sync",
|
||||||
"Sync status": "Sync status",
|
"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",
|
"On": "On",
|
||||||
"Connected email": "Connected email",
|
"Connected email": "Connected email",
|
||||||
"Manual backup": "Manual backup",
|
"Address copied": "Address copied",
|
||||||
"Fetching transactions...": "Fetching transactions...",
|
"Unlocking account": "Unlocking account",
|
||||||
"Loading transactions...": "Loading transactions...",
|
"Decrypting wallet": "Decrypting wallet",
|
||||||
"The transaction URL could not be opened": "The transaction URL could not be opened",
|
"used": "used",
|
||||||
"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.",
|
"Stats": "Stats",
|
||||||
"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.",
|
"Channels you follow": "Channels you follow",
|
||||||
"Learn more": "Learn more",
|
"Suggested": "Suggested",
|
||||||
"Not interested": "Not interested",
|
"ALL": "ALL",
|
||||||
"Get started": "Get started",
|
"Customize your tags": "Customize your tags",
|
||||||
"Fetching rewards...": "Fetching rewards...",
|
"Done": "Done",
|
||||||
"Custom Code": "Custom Code",
|
"Unfollow": "Unfollow",
|
||||||
"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.",
|
"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.",
|
||||||
"Settings": "Settings",
|
"No views": "No views",
|
||||||
|
"%view% views": "%view% views",
|
||||||
"Content": "Content",
|
"Content": "Content",
|
||||||
"Enable background media playback": "Enable background media playback",
|
"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.",
|
"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",
|
"Choose language": "Choose language",
|
||||||
|
"Use device language": "Use device language",
|
||||||
|
"Gujarati": "Gujarati",
|
||||||
"Show mature content": "Show mature content",
|
"Show mature content": "Show mature content",
|
||||||
"Notifications": "Notifications",
|
"Notifications": "Notifications",
|
||||||
"Choose the notifications you would like to receive.": "Choose the notifications you would like to receive.",
|
"Choose the notifications you would like to receive.": "Choose the notifications you would like to receive.",
|
||||||
"Subscriptions": "Subscriptions",
|
|
||||||
"Rewards": "Rewards",
|
|
||||||
"Content Interests": "Content Interests",
|
"Content Interests": "Content Interests",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"Show URL suggestions": "Show URL suggestions",
|
"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",
|
"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.",
|
"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.",
|
||||||
"Loading...": "Loading...",
|
"There's nothing here yet.\nPlease check back later.": "There's nothing here yet.\nPlease check back later.",
|
||||||
"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.",
|
"%follower% followers": "%follower% followers",
|
||||||
"What is LBRY?": "What is LBRY?",
|
"Are you sure you want to tip %amount% credits": "Are you sure you want to tip %amount% credits",
|
||||||
"Android App Basics": "Android App Basics",
|
"Send tip": "Send tip",
|
||||||
"Frequently Asked Questions": "Frequently Asked Questions",
|
"No": "No",
|
||||||
"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.",
|
"Yes": "Yes",
|
||||||
"App info": "App info",
|
"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",
|
"Update mailing preferences": "Update mailing preferences",
|
||||||
"App version": "App version",
|
"Please confirm you want to tip %amount% credits": "Please confirm you want to tip %amount% credits",
|
||||||
"LBRY SDK": "LBRY SDK",
|
"Send Tip": "Send Tip",
|
||||||
"Platform": "Platform",
|
|
||||||
"Installation ID": "Installation ID",
|
|
||||||
"Logs": "Logs",
|
|
||||||
"Send log": "Send log",
|
|
||||||
"Testing network": "Testing network",
|
|
||||||
"Spanish": "Spanish",
|
|
||||||
"Portuguese": "Portuguese",
|
|
||||||
"Polish": "Polish",
|
|
||||||
"Malay": "Malay",
|
|
||||||
"Please wait while we get some things ready...": "Please wait while we get some things ready...",
|
|
||||||
"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",
|
|
||||||
"An account will allow you to earn rewards and keep your account and settings synced.": "An account will allow you to earn rewards and keep your account and settings synced.",
|
|
||||||
"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)",
|
|
||||||
"Change email": "Change email",
|
|
||||||
"Your email address was successfully verified.": "Your email address was successfully verified.",
|
|
||||||
"Retrieving your account information...": "Retrieving your account information...",
|
|
||||||
"Synchronizing": "Synchronizing",
|
|
||||||
"Validating password": "Validating password",
|
|
||||||
"Invalid password specified. Please enter the password for your previously synchronised wallet.": "Invalid password specified. Please enter the password for your previously synchronised wallet.",
|
|
||||||
"Please enter the password you used to secure your wallet.": "Please enter the password you used to secure your wallet.",
|
|
||||||
"If you did not provide a password, please press Use LBRY to continue.": "If you did not provide a password, please press Use LBRY to continue.",
|
|
||||||
"Note: for wallet security purposes, LBRY is unable to reset your password.": "Note: for wallet security purposes, LBRY is unable to reset your password.",
|
|
||||||
"More tags you follow": "More tags you follow",
|
|
||||||
"Blockchain Sync": "Blockchain Sync",
|
|
||||||
"Network Loading": "Network Loading",
|
|
||||||
"Initializing LBRY service": "Initializing LBRY service",
|
|
||||||
"Sign In": "Sign In",
|
|
||||||
"SIGN IN": "SIGN IN",
|
|
||||||
"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!",
|
|
||||||
"Send verification email": "Send verification email",
|
|
||||||
"You do not have any\ndownloaded content on this device.": "You do not have any\ndownloaded content on this device.",
|
"You do not have any\ndownloaded content on this device.": "You do not have any\ndownloaded content on this device.",
|
||||||
"It looks like you have not\npublished any content to LBRY yet.": "It looks like you have not\npublished any content to LBRY yet.",
|
"%view% view": "%view% view",
|
||||||
"An account with LBRY Inc. allows you to earn rewards, backup your wallet, and keep everything synced.": "An account with LBRY Inc. allows you to earn rewards, backup your wallet, and keep everything synced.",
|
"No channel name after @.": "No channel name after @.",
|
||||||
"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.",
|
"Loading transactions...": "Loading transactions...",
|
||||||
"Skip Account": "Skip Account",
|
"Pending": "Pending",
|
||||||
"Sign Up": "Sign Up",
|
"Phone Number": "Phone Number",
|
||||||
"Indonesian": "Indonesian",
|
"Please provide a phone number to prevent fraud.": "Please provide a phone number to prevent fraud.",
|
||||||
"Hindi": "Hindi",
|
"Send verification text": "Send verification text",
|
||||||
"Verify Email": "Verify Email",
|
"Open": "Open",
|
||||||
"Resend": "Resend",
|
"for": "for",
|
||||||
"Edit": "Edit",
|
|
||||||
"Please follow the instructions in the email sent to your address to continue.": "Please follow the instructions in the email sent to your address to continue.",
|
|
||||||
"Please enter a password to secure your account and wallet.": "Please enter a password to secure your account and wallet.",
|
|
||||||
"Anonymous": "Anonymous",
|
|
||||||
"Connecting...": "Connecting...",
|
|
||||||
"Gujarati": "Gujarati",
|
|
||||||
"Turkish": "Turkish",
|
|
||||||
"Italian": "Italian",
|
|
||||||
"English": "English",
|
|
||||||
"Use device language": "Use device language",
|
|
||||||
"Tags you follow": "Tags you follow",
|
|
||||||
"Everyone": "Everyone",
|
"Everyone": "Everyone",
|
||||||
"Greek": "Greek",
|
"Suggested channels": "Suggested channels",
|
||||||
"Romanian": "Romanian",
|
"You might also like": "You might also like",
|
||||||
"Norwegian": "Norwegian",
|
"Publish something new": "Publish something new",
|
||||||
"Korean": "Korean",
|
"Filter for": "Filter for",
|
||||||
"Cambodian": "Cambodian",
|
"Tags you follow": "Tags you follow",
|
||||||
"Croatian": "Croatian",
|
|
||||||
"Czech": "Czech",
|
|
||||||
"Arabic": "Arabic",
|
|
||||||
"Thai": "Thai",
|
|
||||||
"Vietnamese": "Vietnamese",
|
|
||||||
"Dutch": "Dutch",
|
|
||||||
"Russian": "Russian",
|
|
||||||
"Japanese": "Japanese",
|
|
||||||
"German": "German",
|
|
||||||
"Trending": "Trending",
|
|
||||||
"Channels": "Channels",
|
|
||||||
"Library": "Library",
|
|
||||||
"Publishes": "Publishes",
|
|
||||||
"New Publish": "New Publish",
|
|
||||||
"About": "About",
|
|
||||||
"Sort content by": "Sort content by",
|
"Sort content by": "Sort content by",
|
||||||
"Trending content": "Trending content",
|
"Trending content": "Trending content",
|
||||||
"New content": "New content",
|
"New content": "New content",
|
||||||
"Top content": "Top content",
|
"Top content": "Top content",
|
||||||
"Search movies, music, and more": "Search movies, music, and more",
|
"Top": "Top",
|
||||||
"Please provide a valid email address to continue.": "Please provide a valid email address to continue.",
|
"Past week": "Past week",
|
||||||
"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.",
|
"It looks like you have not\npublished any content to LBRY yet.": "It looks like you have not\npublished any content to LBRY yet.",
|
||||||
"%num% blocks behind": "%num% blocks behind",
|
|
||||||
"Catching up with the blockchain ($%progress%%)": "Catching up with the blockchain ($%progress%%)",
|
|
||||||
"Catching up with the blockchain (%progress%%)": "Catching up with the blockchain (%progress%%)",
|
|
||||||
"Get %amount% free credits after creating an account.": "Get %amount% free credits after creating an account.",
|
|
||||||
"Off": "Off",
|
|
||||||
"Sync FAQ": "Sync FAQ",
|
|
||||||
"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.",
|
|
||||||
"%amount% free credits available in rewards.": "%amount% free credits available in rewards.",
|
|
||||||
"Tap to learn more.": "Tap to learn more.",
|
|
||||||
"password": "password",
|
|
||||||
"Enable sync": "Enable sync",
|
|
||||||
"Disable wallet sync": "Disable wallet sync",
|
|
||||||
"Are you sure you want to turn off wallet sync?": "Are you sure you want to turn off wallet sync?",
|
|
||||||
"No": "No",
|
|
||||||
"Yes": "Yes",
|
|
||||||
"Wallet sync was successfully disabled.": "Wallet sync was successfully disabled.",
|
|
||||||
"more": "more",
|
|
||||||
"Related Content": "Related Content",
|
|
||||||
"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.",
|
|
||||||
"Download": "Download",
|
|
||||||
"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.",
|
|
||||||
"Stop": "Stop",
|
"Stop": "Stop",
|
||||||
"%progress%% complete": "%progress%% complete",
|
"Unsupported Content": "Unsupported Content",
|
||||||
"Customize your tags": "Customize your tags",
|
"all tags you follow": "all tags you follow",
|
||||||
"Done": "Done",
|
"Send LBC": "Send LBC",
|
||||||
"Search for more tags": "Search for more tags",
|
"from": "from",
|
||||||
"Send": "Send",
|
"This file cannot be displayed in the LBRY app.": "This file cannot be displayed in the LBRY app.",
|
||||||
"Recent Transactions": "Recent Transactions",
|
"Please press the Play button.": "Please press the Play button.",
|
||||||
"Nothing here. Publish something!": "Nothing here. Publish something!",
|
"Stop download": "Stop download",
|
||||||
"CONTENT": "CONTENT",
|
"Are you sure you want to stop downloading this file?": "Are you sure you want to stop downloading this file?",
|
||||||
"ABOUT": "ABOUT",
|
"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.",
|
||||||
"New": "New",
|
"Connection Failure": "Connection Failure",
|
||||||
"Content Freedom": "Content Freedom",
|
"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.",
|
||||||
"Get Social": "Get Social",
|
"%num% block behind": "%num% block behind",
|
||||||
"used": "used",
|
"Delete files": "Delete files",
|
||||||
"Stats": "Stats",
|
"Are you sure you want to delete the selected content?": "Are you sure you want to delete the selected content?",
|
||||||
"Welcome to LBRY.": "Welcome to LBRY.",
|
"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.",
|
||||||
"Account Recommended": "Account Recommended",
|
"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.",
|
||||||
"%amount% available credits": "%amount% available credits",
|
"Camera": "Camera",
|
||||||
"%amount% available credit": "%amount% available credit"
|
"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"
|
||||||
}
|
}
|
10433
package-lock.json
generated
10433
package-lock.json
generated
File diff suppressed because it is too large
Load diff
159
package.json
159
package.json
|
@ -1,73 +1,90 @@
|
||||||
{
|
{
|
||||||
"name": "LBRYApp",
|
"name": "LBRYApp",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": "true",
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
"android": "react-native run-android",
|
||||||
"devtools": "react-devtools",
|
"ios": "react-native run-ios",
|
||||||
"format": "prettier 'src/**/*.{js,json}' --write",
|
"start": "react-native start",
|
||||||
"precommit": "lint-staged"
|
"test": "jest",
|
||||||
},
|
"lint": "eslint .",
|
||||||
"dependencies": {
|
"format": "prettier 'src/**/*.{js,json}' --write",
|
||||||
"base-64": "^0.1.0",
|
"precommit": "lint-staged"
|
||||||
"@expo/vector-icons": "^8.1.0",
|
},
|
||||||
"gfycat-style-urls": "^1.0.3",
|
"dependencies": {
|
||||||
"lbry-redux": "lbryio/lbry-redux#9b11cfed62af7f0f8eb6fa3c88a1f8d2cce46afc",
|
"base-64": "^0.1.0",
|
||||||
"lbryinc": "lbryio/lbryinc#9ffb883cc11e36d55a729c65b70c405a4a56d35e",
|
"@expo/vector-icons": "^8.1.0",
|
||||||
"lodash": ">=4.17.11",
|
"gfycat-style-urls": "^1.0.3",
|
||||||
"merge": ">=1.2.1",
|
"lbry-redux": "lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017",
|
||||||
"moment": "^2.22.1",
|
"lbryinc": "lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270",
|
||||||
"react": "16.8.6",
|
"lodash": ">=4.17.11",
|
||||||
"react-native": "0.59.10",
|
"merge": ">=1.2.1",
|
||||||
"@react-native-community/async-storage": "^1.5.1",
|
"moment": "^2.22.1",
|
||||||
"react-native-camera": "^2.11.1",
|
"react": "16.9.0",
|
||||||
"react-native-country-picker-modal": "^0.6.2",
|
"react-native": "0.61.5",
|
||||||
"react-native-exception-handler": "2.9.0",
|
"@react-native-community/async-storage": "^1.5.1",
|
||||||
"react-native-fast-image": "^6.1.1",
|
"@react-native-community/masked-view": "^0.1.5",
|
||||||
"react-native-fs": "^2.13.3",
|
"react-native-camera": "^3.15.0",
|
||||||
"react-native-gesture-handler": "1.3.0",
|
"react-native-country-picker-modal": "^1.10.0",
|
||||||
"react-native-image-zoom-viewer": "^2.2.5",
|
"react-native-exception-handler": "2.10.8",
|
||||||
"react-native-password-strength-meter": "^0.0.2",
|
"react-native-fast-image": "^7.0.2",
|
||||||
"react-native-phone-input": "lbryio/react-native-phone-input",
|
"react-native-fs": "^2.16.6",
|
||||||
"react-native-reanimated": "1.2.0",
|
"react-native-gesture-handler": "1.5.2",
|
||||||
"react-native-super-grid": "^3.0.4",
|
"react-native-image-zoom-viewer": "^2.2.5",
|
||||||
"react-native-vector-icons": "^6.6.0",
|
"react-native-password-strength-meter": "^0.0.2",
|
||||||
"react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
|
"react-native-phone-input": "lbryio/react-native-phone-input",
|
||||||
"react-navigation": "^3.11.0",
|
"react-native-progress-circle": "2.1.0",
|
||||||
"react-navigation-drawer": "^1.4.0",
|
"react-native-reanimated": "1.4.0",
|
||||||
"react-navigation-redux-helpers": "^3.0.2",
|
"react-native-safe-area-context": "^0.6.2",
|
||||||
"react-navigation-stack": "^1.8.1",
|
"react-native-screens": "^2.0.0",
|
||||||
"react-redux": "^5.0.3",
|
"react-native-snackbar": "2.0.4",
|
||||||
"redux": "^4.0.4",
|
"react-native-super-grid": "^3.0.4",
|
||||||
"redux-persist": "^5.10.0",
|
"react-native-vector-icons": "^6.6.0",
|
||||||
"redux-persist-filesystem-storage": "^1.3.2",
|
"react-native-video": "lbryio/react-native-video#7992ff945872f9bd00a3736d9ff1318f343abf47",
|
||||||
"redux-persist-transform-compress": "^4.2.0",
|
"react-native-webview": "^8.0.2",
|
||||||
"redux-persist-transform-filter": "0.0.18",
|
"react-navigation": "^4.0.10",
|
||||||
"redux-thunk": "^2.3.0",
|
"react-navigation-drawer": "2.3.3",
|
||||||
"rn-fetch-blob": "0.10.15",
|
"react-navigation-redux-helpers": "^3.0.2",
|
||||||
"seedrandom": "3.0.3"
|
"react-navigation-tabs": "^2.7.0",
|
||||||
},
|
"react-navigation-stack": "^1.10.3",
|
||||||
"devDependencies": {
|
"react-redux": "^5.0.3",
|
||||||
"@babel/core": "^7.5.4",
|
"redux": "^4.0.4",
|
||||||
"babel-eslint": "10.0.2",
|
"redux-persist": "^6.0.0",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
|
"redux-persist-filesystem-storage": "^2.1.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"redux-persist-transform-compress": "^4.2.0",
|
||||||
"babel-preset-stage-2": "^6.18.0",
|
"redux-persist-transform-filter": "0.0.18",
|
||||||
"babel-plugin-module-resolver": "^3.1.1",
|
"redux-thunk": "^2.3.0",
|
||||||
"eslint": "^5.16.0",
|
"rn-fetch-blob": "0.12.0",
|
||||||
"eslint-config-standard": "^12.0.0",
|
"seedrandom": "3.0.3",
|
||||||
"eslint-config-standard-jsx": "^6.0.2",
|
"showdown": "1.9.1"
|
||||||
"eslint-plugin-flowtype": "^2.46.1",
|
},
|
||||||
"eslint-plugin-import": "^2.17.2",
|
"devDependencies": {
|
||||||
"eslint-plugin-node": "^8.0.1",
|
"@babel/core": "^7.6.2",
|
||||||
"eslint-plugin-promise": "^4.1.1",
|
"babel-eslint": "10.0.2",
|
||||||
"eslint-plugin-react": "^7.12.4",
|
"@babel/plugin-proposal-object-rest-spread": "^7.5.4",
|
||||||
"eslint-plugin-standard": "^4.0.0",
|
"babel-preset-env": "^1.6.1",
|
||||||
"flow-babel-webpack-plugin": "^1.1.1",
|
"babel-preset-stage-2": "^6.18.0",
|
||||||
"husky": "^0.14.3",
|
"babel-plugin-module-resolver": "^3.1.1",
|
||||||
"lint-staged": "^7.0.4",
|
"eslint": "^6.5.1",
|
||||||
"metro-react-native-babel-preset": "^0.55.0",
|
"eslint-config-standard": "^12.0.0",
|
||||||
"prettier": "^1.11.1",
|
"eslint-config-standard-jsx": "^6.0.2",
|
||||||
"react-devtools": "^3.6.3"
|
"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
12
reactotron.js
Normal 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;
|
|
@ -5,7 +5,9 @@ import DiscoverPage from 'page/discover';
|
||||||
import DownloadsPage from 'page/downloads';
|
import DownloadsPage from 'page/downloads';
|
||||||
import DrawerContent from 'component/drawerContent';
|
import DrawerContent from 'component/drawerContent';
|
||||||
import FilePage from 'page/file';
|
import FilePage from 'page/file';
|
||||||
|
import LiteFilePage from 'page/liteFile';
|
||||||
import FirstRunScreen from 'page/firstRun';
|
import FirstRunScreen from 'page/firstRun';
|
||||||
|
import InvitesPage from 'page/invites';
|
||||||
import PublishPage from 'page/publish';
|
import PublishPage from 'page/publish';
|
||||||
import PublishesPage from 'page/publishes';
|
import PublishesPage from 'page/publishes';
|
||||||
import RewardsPage from 'page/rewards';
|
import RewardsPage from 'page/rewards';
|
||||||
|
@ -18,7 +20,7 @@ import SubscriptionsPage from 'page/subscriptions';
|
||||||
import TransactionHistoryPage from 'page/transactionHistory';
|
import TransactionHistoryPage from 'page/transactionHistory';
|
||||||
import VerificationScreen from 'page/verification';
|
import VerificationScreen from 'page/verification';
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from 'page/wallet';
|
||||||
import { NavigationActions } from 'react-navigation';
|
import { NavigationActions, StackActions } from 'react-navigation';
|
||||||
import { createDrawerNavigator } from 'react-navigation-drawer';
|
import { createDrawerNavigator } from 'react-navigation-drawer';
|
||||||
import { createStackNavigator } from 'react-navigation-stack';
|
import { createStackNavigator } from 'react-navigation-stack';
|
||||||
import {
|
import {
|
||||||
|
@ -29,6 +31,7 @@ import {
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
|
Alert,
|
||||||
BackHandler,
|
BackHandler,
|
||||||
DeviceEventEmitter,
|
DeviceEventEmitter,
|
||||||
Linking,
|
Linking,
|
||||||
|
@ -38,9 +41,23 @@ import {
|
||||||
ToastAndroid,
|
ToastAndroid,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||||
import { SETTINGS, doDismissToast, doPopulateSharedUserState, doPreferenceGet, doToast, selectToast } from 'lbry-redux';
|
import {
|
||||||
|
Lbry,
|
||||||
|
ACTIONS,
|
||||||
|
SETTINGS,
|
||||||
|
doBalanceSubscribe,
|
||||||
|
doDismissToast,
|
||||||
|
doPopulateSharedUserState,
|
||||||
|
doPreferenceGet,
|
||||||
|
doToast,
|
||||||
|
selectToast,
|
||||||
|
} from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
Lbryio,
|
Lbryio,
|
||||||
|
rewards as REWARD_TYPES,
|
||||||
|
doBlackListedOutpointsSubscribe,
|
||||||
|
doClaimRewardType,
|
||||||
|
doFilteredOutpointsSubscribe,
|
||||||
doGetSync,
|
doGetSync,
|
||||||
doUserCheckEmailVerified,
|
doUserCheckEmailVerified,
|
||||||
doUserEmailVerify,
|
doUserEmailVerify,
|
||||||
|
@ -51,6 +68,7 @@ import {
|
||||||
selectHashChanged,
|
selectHashChanged,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
|
import { doStartDownload, doUpdateDownload, doCompleteDownload } from 'redux/actions/file';
|
||||||
import { makeSelectClientSetting, selectFullscreenMode } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectFullscreenMode } from 'redux/selectors/settings';
|
||||||
import { decode as atob } from 'base-64';
|
import { decode as atob } from 'base-64';
|
||||||
import { dispatchNavigateBack, dispatchNavigateToUri, transformUrl } from 'utils/helper';
|
import { dispatchNavigateBack, dispatchNavigateToUri, transformUrl } from 'utils/helper';
|
||||||
|
@ -62,27 +80,20 @@ import NavigationButton from 'component/navigationButton';
|
||||||
import discoverStyle from 'styles/discover';
|
import discoverStyle from 'styles/discover';
|
||||||
import searchStyle from 'styles/search';
|
import searchStyle from 'styles/search';
|
||||||
import SearchRightHeaderIcon from 'component/searchRightHeaderIcon';
|
import SearchRightHeaderIcon from 'component/searchRightHeaderIcon';
|
||||||
|
import Snackbar from 'react-native-snackbar';
|
||||||
|
import { doSetSdkReady } from 'redux/actions/settings';
|
||||||
|
|
||||||
const SYNC_GET_INTERVAL = 1000 * 60 * 5; // every 5 minutes
|
const SYNC_GET_INTERVAL = 1000 * 60 * 5; // every 5 minutes
|
||||||
|
|
||||||
const menuNavigationButton = navigation => (
|
|
||||||
<NavigationButton
|
|
||||||
name="bars"
|
|
||||||
size={24}
|
|
||||||
style={discoverStyle.drawerMenuButton}
|
|
||||||
iconStyle={discoverStyle.drawerHamburger}
|
|
||||||
onPress={() => navigation.openDrawer()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const discoverStack = createStackNavigator(
|
const discoverStack = createStackNavigator(
|
||||||
{
|
{
|
||||||
Discover: {
|
Subscriptions: {
|
||||||
screen: DiscoverPage,
|
screen: SubscriptionsPage,
|
||||||
navigationOptions: ({ navigation }) => ({
|
navigationOptions: {
|
||||||
title: 'Explore',
|
title: 'Following',
|
||||||
header: null,
|
header: null,
|
||||||
}),
|
drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={drawerIconSize} style={{ color: tintColor }} />,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
File: {
|
File: {
|
||||||
screen: FilePage,
|
screen: FilePage,
|
||||||
|
@ -106,7 +117,7 @@ const discoverStack = createStackNavigator(
|
||||||
{
|
{
|
||||||
headerMode: 'screen',
|
headerMode: 'screen',
|
||||||
transitionConfig: () => ({ screenInterpolator: () => null }),
|
transitionConfig: () => ({ screenInterpolator: () => null }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
discoverStack.navigationOptions = ({ navigation }) => {
|
discoverStack.navigationOptions = ({ navigation }) => {
|
||||||
|
@ -140,7 +151,7 @@ const walletStack = createStackNavigator(
|
||||||
{
|
{
|
||||||
headerMode: 'screen',
|
headerMode: 'screen',
|
||||||
transitionConfig: () => ({ screenInterpolator: () => null }),
|
transitionConfig: () => ({ screenInterpolator: () => null }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const drawerIconSize = 18;
|
const drawerIconSize = 18;
|
||||||
|
@ -149,10 +160,17 @@ const drawer = createDrawerNavigator(
|
||||||
DiscoverStack: {
|
DiscoverStack: {
|
||||||
screen: discoverStack,
|
screen: discoverStack,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
title: 'Explore',
|
title: 'Following',
|
||||||
drawerIcon: ({ tintColor }) => <Icon name="home" size={drawerIconSize} style={{ color: tintColor }} />,
|
drawerIcon: ({ tintColor }) => <Icon name="home" size={drawerIconSize} style={{ color: tintColor }} />,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Discover: {
|
||||||
|
screen: DiscoverPage,
|
||||||
|
navigationOptions: ({ navigation }) => ({
|
||||||
|
title: 'Your Tags',
|
||||||
|
header: null,
|
||||||
|
}),
|
||||||
|
},
|
||||||
Trending: {
|
Trending: {
|
||||||
screen: TrendingPage,
|
screen: TrendingPage,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
@ -160,13 +178,6 @@ const drawer = createDrawerNavigator(
|
||||||
drawerIcon: ({ tintColor }) => <Icon name="fire" size={drawerIconSize} style={{ color: tintColor }} />,
|
drawerIcon: ({ tintColor }) => <Icon name="fire" size={drawerIconSize} style={{ color: tintColor }} />,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Subscriptions: {
|
|
||||||
screen: SubscriptionsPage,
|
|
||||||
navigationOptions: {
|
|
||||||
title: 'Subscriptions',
|
|
||||||
drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={drawerIconSize} style={{ color: tintColor }} />,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
WalletStack: {
|
WalletStack: {
|
||||||
screen: walletStack,
|
screen: walletStack,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
@ -200,6 +211,12 @@ const drawer = createDrawerNavigator(
|
||||||
drawerIcon: ({ tintColor }) => <Icon name="award" size={drawerIconSize} style={{ color: tintColor }} />,
|
drawerIcon: ({ tintColor }) => <Icon name="award" size={drawerIconSize} style={{ color: tintColor }} />,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Invites: {
|
||||||
|
screen: InvitesPage,
|
||||||
|
navigationOptions: {
|
||||||
|
drawerIcon: ({ tintColor }) => <Icon name="user-friends" size={drawerIconSize} style={{ color: tintColor }} />,
|
||||||
|
},
|
||||||
|
},
|
||||||
Downloads: {
|
Downloads: {
|
||||||
screen: DownloadsPage,
|
screen: DownloadsPage,
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
|
@ -223,17 +240,18 @@ const drawer = createDrawerNavigator(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
drawerWidth: 300,
|
drawerWidth: 299,
|
||||||
drawerBackgroundColor: 'transparent',
|
drawerBackgroundColor: 'transparent',
|
||||||
headerMode: 'none',
|
headerMode: 'none',
|
||||||
backBehavior: 'none',
|
backBehavior: 'none',
|
||||||
unmountInactiveRoutes: true,
|
unmountInactiveRoutes: true,
|
||||||
contentComponent: DrawerContent,
|
contentComponent: DrawerContent,
|
||||||
|
overlayColor: 'rgba(0, 0, 0, 0.7)',
|
||||||
contentOptions: {
|
contentOptions: {
|
||||||
activeTintColor: Colors.LbryGreen,
|
activeTintColor: Colors.LbryGreen,
|
||||||
labelStyle: discoverStyle.menuText,
|
labelStyle: discoverStyle.menuText,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const mainStackNavigator = new createStackNavigator(
|
const mainStackNavigator = new createStackNavigator(
|
||||||
|
@ -259,10 +277,16 @@ const mainStackNavigator = new createStackNavigator(
|
||||||
drawerLockMode: 'locked-closed',
|
drawerLockMode: 'locked-closed',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
LiteFile: {
|
||||||
|
screen: LiteFilePage,
|
||||||
|
navigationOptions: {
|
||||||
|
drawerLockMode: 'locked-closed',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerMode: 'none',
|
headerMode: 'none',
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const AppNavigator = mainStackNavigator;
|
export const AppNavigator = mainStackNavigator;
|
||||||
|
@ -301,7 +325,7 @@ class AppWithNavigationState extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,6 +334,12 @@ class AppWithNavigationState extends React.Component {
|
||||||
this.emailVerifyCheckInterval = setInterval(() => this.checkEmailVerification(), 5000);
|
this.emailVerifyCheckInterval = setInterval(() => this.checkEmailVerification(), 5000);
|
||||||
Linking.addEventListener('url', this._handleUrl);
|
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
|
// call /sync/get with interval
|
||||||
this.syncGetInterval = setInterval(() => {
|
this.syncGetInterval = setInterval(() => {
|
||||||
this.setState({ syncHashChanged: false }); // reset local state
|
this.setState({ syncHashChanged: false }); // reset local state
|
||||||
|
@ -340,11 +370,115 @@ class AppWithNavigationState extends React.Component {
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
/* failed */
|
/* 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() {
|
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);
|
AppState.removeEventListener('change', this._handleAppStateChange);
|
||||||
BackHandler.removeEventListener('hardwareBackPress');
|
BackHandler.removeEventListener('hardwareBackPress');
|
||||||
Linking.removeEventListener('url', this._handleUrl);
|
Linking.removeEventListener('url', this._handleUrl);
|
||||||
|
@ -364,7 +498,7 @@ class AppWithNavigationState extends React.Component {
|
||||||
this.setState({ verifyPending: false });
|
this.setState({ verifyPending: false });
|
||||||
|
|
||||||
NativeModules.Firebase.track('email_verified', { email: user.primary_email });
|
NativeModules.Firebase.track('email_verified', { email: user.primary_email });
|
||||||
ToastAndroid.show(__('Your email address was successfully verified.'), ToastAndroid.LONG);
|
Snackbar.show({ title: __('Your email address was successfully verified.'), duration: Snackbar.LENGTH_LONG });
|
||||||
|
|
||||||
// get user settings after email verification
|
// get user settings after email verification
|
||||||
this.getUserSettings();
|
this.getUserSettings();
|
||||||
|
@ -381,7 +515,7 @@ class AppWithNavigationState extends React.Component {
|
||||||
const { toast, emailToVerify, emailVerifyPending, emailVerifyErrorMessage, user } = nextProps;
|
const { toast, emailToVerify, emailVerifyPending, emailVerifyErrorMessage, user } = nextProps;
|
||||||
|
|
||||||
if (toast) {
|
if (toast) {
|
||||||
const { message } = toast;
|
const { message, isError } = toast;
|
||||||
let currentDisplayType;
|
let currentDisplayType;
|
||||||
if (!currentDisplayType && message) {
|
if (!currentDisplayType && message) {
|
||||||
// default to toast if no display type set and there is a message specified
|
// default to toast if no display type set and there is a message specified
|
||||||
|
@ -389,7 +523,14 @@ class AppWithNavigationState extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDisplayType === 'toast') {
|
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());
|
dispatch(doDismissToast());
|
||||||
|
@ -468,7 +609,7 @@ class AppWithNavigationState extends React.Component {
|
||||||
try {
|
try {
|
||||||
verification = JSON.parse(atob(evt.url.substring(15)));
|
verification = JSON.parse(atob(evt.url.substring(15)));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
// console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verification.token && verification.recaptcha) {
|
if (verification.token && verification.recaptcha) {
|
||||||
|
@ -484,7 +625,7 @@ class AppWithNavigationState extends React.Component {
|
||||||
dispatch(
|
dispatch(
|
||||||
doToast({
|
doToast({
|
||||||
message: 'Invalid Verification URI',
|
message: 'Invalid Verification URI',
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -19,7 +19,4 @@ const perform = dispatch => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(ChannelIconItem);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(ChannelIconItem);
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default class ChannelIconItem extends React.PureComponent {
|
||||||
<TouchableOpacity style={channelIconStyle.container} onPress={onPress}>
|
<TouchableOpacity style={channelIconStyle.container} onPress={onPress}>
|
||||||
{isResolvingUri && (
|
{isResolvingUri && (
|
||||||
<View style={channelIconStyle.centered}>
|
<View style={channelIconStyle.centered}>
|
||||||
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<View
|
<View
|
||||||
|
|
|
@ -19,11 +19,11 @@ const select = state => ({
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
|
||||||
getSync: (password, callback) => dispatch(doGetSync(password, callback)),
|
getSync: (password, callback) => dispatch(doGetSync(password, callback)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
perform,
|
||||||
)(ChannelSelector);
|
)(ChannelSelector);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CLAIM_VALUES, isNameValid } from 'lbry-redux';
|
import { CLAIM_VALUES, formatCredits, isNameValid } from 'lbry-redux';
|
||||||
import { ActivityIndicator, NativeModules, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
import { ActivityIndicator, NativeModules, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||||
import { logPublish } from 'utils/helper';
|
import { logPublish } from 'utils/helper';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
@ -14,6 +14,7 @@ export default class ChannelSelector extends React.PureComponent {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
creditsInputFocused: false,
|
||||||
currentSelectedValue: Constants.ITEM_ANONYMOUS,
|
currentSelectedValue: Constants.ITEM_ANONYMOUS,
|
||||||
newChannelName: '',
|
newChannelName: '',
|
||||||
newChannelBid: 0.1,
|
newChannelBid: 0.1,
|
||||||
|
@ -35,10 +36,14 @@ export default class ChannelSelector extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { channels: prevChannels = [], channelName } = this.props;
|
const { channels: prevChannels = [], channelName: prevChannelName } = this.props;
|
||||||
const { channels = [] } = nextProps;
|
const { channels = [], channelName } = nextProps;
|
||||||
|
|
||||||
if (channels && channels.length !== prevChannels.length && channelName !== this.state.currentSelectedValue) {
|
if (channels && channels.length !== prevChannels.length && channelName !== this.state.currentSelectedValue) {
|
||||||
|
this.setState({ currentSelectedValue: prevChannelName });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelName !== prevChannelName) {
|
||||||
this.setState({ currentSelectedValue: channelName });
|
this.setState({ currentSelectedValue: channelName });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,10 +193,11 @@ export default class ChannelSelector extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
||||||
const { enabled, fetchingChannels, channels = [] } = this.props;
|
const { balance, enabled, fetchingChannels, channels = [], showAnonymous } = this.props;
|
||||||
const pickerItems = [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].concat(
|
const pickerItems = (showAnonymous
|
||||||
channels ? channels.map(ch => ch.name) : []
|
? [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL]
|
||||||
);
|
: [Constants.ITEM_CREATE_A_CHANNEL]
|
||||||
|
).concat(channels ? channels.map(ch => ch.name) : []);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
newChannelName,
|
newChannelName,
|
||||||
|
@ -244,18 +250,26 @@ export default class ChannelSelector extends React.PureComponent {
|
||||||
style={channelSelectorStyle.bidAmountInput}
|
style={channelSelectorStyle.bidAmountInput}
|
||||||
value={String(newChannelBid)}
|
value={String(newChannelBid)}
|
||||||
onChangeText={this.handleNewChannelBidChange}
|
onChangeText={this.handleNewChannelBidChange}
|
||||||
|
onFocus={() => this.setState({ creditsInputFocused: true })}
|
||||||
|
onBlur={() => this.setState({ creditsInputFocused: false })}
|
||||||
placeholder={'0.00'}
|
placeholder={'0.00'}
|
||||||
keyboardType={'number-pad'}
|
keyboardType={'number-pad'}
|
||||||
underlineColorAndroid={Colors.NextLbryGreen}
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
/>
|
/>
|
||||||
<Text style={channelSelectorStyle.currency}>LBC</Text>
|
<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>
|
</View>
|
||||||
<Text style={channelSelectorStyle.helpText}>
|
<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>
|
</Text>
|
||||||
|
|
||||||
<View style={channelSelectorStyle.buttonContainer}>
|
<View style={channelSelectorStyle.buttonContainer}>
|
||||||
{creatingChannel && <ActivityIndicator size={'small'} color={Colors.LbryGreen} />}
|
{creatingChannel && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
|
||||||
{!creatingChannel && (
|
{!creatingChannel && (
|
||||||
<View style={channelSelectorStyle.buttons}>
|
<View style={channelSelectorStyle.buttons}>
|
||||||
<Link style={channelSelectorStyle.cancelLink} text={__('Cancel')} onPress={this.handleCreateCancel} />
|
<Link style={channelSelectorStyle.cancelLink} text={__('Cancel')} onPress={this.handleCreateCancel} />
|
||||||
|
|
|
@ -12,18 +12,15 @@ import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import ClaimList from './view';
|
import ClaimList from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
showNsfwContent: selectShowNsfw(state),
|
|
||||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||||
lastPageReached: selectClaimSearchByQueryLastPageReached(state),
|
lastPageReached: selectClaimSearchByQueryLastPageReached(state),
|
||||||
loadingByQuery: selectFetchingClaimSearchByQuery(state),
|
loadingByQuery: selectFetchingClaimSearchByQuery(state),
|
||||||
loading: selectFetchingClaimSearch(state),
|
loading: selectFetchingClaimSearch(state),
|
||||||
|
showNsfwContent: selectShowNsfw(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
claimSearch: options => dispatch(doClaimSearch(options)),
|
claimSearch: options => dispatch(doClaimSearch(options)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(ClaimList);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(ClaimList);
|
|
||||||
|
|
39
src/component/claimResultItem/index.js
Normal file
39
src/component/claimResultItem/index.js
Normal 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);
|
180
src/component/claimResultItem/view.js
Normal file
180
src/component/claimResultItem/view.js
Normal 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;
|
|
@ -1,18 +1,19 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doFetchChannelListMine, selectMyChannelClaims } from 'lbry-redux';
|
import { doToast, selectBalance, selectMyChannelClaims } from 'lbry-redux';
|
||||||
import { selectUser } from 'lbryinc';
|
import { selectUnclaimedRewardValue, selectUser } from 'lbryinc';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
import DrawerContent from './view';
|
import DrawerContent from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
balance: selectBalance(state),
|
||||||
channels: selectMyChannelClaims(state),
|
channels: selectMyChannelClaims(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
|
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
notify: data => dispatch(doToast(data)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(DrawerContent);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(DrawerContent);
|
|
||||||
|
|
|
@ -6,11 +6,13 @@ import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import channelIconStyle from 'styles/channelIcon';
|
import channelIconStyle from 'styles/channelIcon';
|
||||||
import discoverStyle from 'styles/discover';
|
import discoverStyle from 'styles/discover';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import { formatUsd } from 'utils/helper';
|
||||||
|
|
||||||
const groupedMenuItems = {
|
const groupedMenuItems = {
|
||||||
'Find content': [
|
'Find content': [
|
||||||
|
{ icon: 'heart', solid: true, label: 'Following', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS },
|
||||||
{ icon: 'hashtag', label: 'Your Tags', route: Constants.DRAWER_ROUTE_DISCOVER },
|
{ icon: 'hashtag', label: 'Your Tags', route: Constants.DRAWER_ROUTE_DISCOVER },
|
||||||
{ icon: 'heart', solid: true, label: 'Subscriptions', route: Constants.DRAWER_ROUTE_SUBSCRIPTIONS },
|
|
||||||
{ icon: 'globe-americas', label: 'All Content', route: Constants.DRAWER_ROUTE_TRENDING },
|
{ icon: 'globe-americas', label: 'All Content', route: Constants.DRAWER_ROUTE_TRENDING },
|
||||||
],
|
],
|
||||||
'Your content': [
|
'Your content': [
|
||||||
|
@ -22,6 +24,7 @@ const groupedMenuItems = {
|
||||||
Wallet: [
|
Wallet: [
|
||||||
{ icon: 'wallet', label: 'Wallet', route: Constants.DRAWER_ROUTE_WALLET },
|
{ icon: 'wallet', label: 'Wallet', route: Constants.DRAWER_ROUTE_WALLET },
|
||||||
{ icon: 'award', label: 'Rewards', route: Constants.DRAWER_ROUTE_REWARDS },
|
{ icon: 'award', label: 'Rewards', route: Constants.DRAWER_ROUTE_REWARDS },
|
||||||
|
{ icon: 'user-friends', label: 'Invites', route: Constants.DRAWER_ROUTE_INVITES },
|
||||||
],
|
],
|
||||||
Settings: [
|
Settings: [
|
||||||
{ icon: 'cog', label: 'Settings', route: Constants.DRAWER_ROUTE_SETTINGS },
|
{ icon: 'cog', label: 'Settings', route: Constants.DRAWER_ROUTE_SETTINGS },
|
||||||
|
@ -30,11 +33,27 @@ const groupedMenuItems = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const groupNames = Object.keys(groupedMenuItems);
|
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 {
|
class DrawerContent extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
usdExchangeRate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { fetchChannelListMine } = this.props;
|
Lbryio.getExchangeRates().then(rates => {
|
||||||
fetchChannelListMine();
|
if (!isNaN(rates.LBC_USD)) {
|
||||||
|
this.setState({ usdExchangeRate: rates.LBC_USD });
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAvatarImageUrl = () => {
|
getAvatarImageUrl = () => {
|
||||||
|
@ -61,8 +80,23 @@ class DrawerContent extends React.PureComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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() {
|
render() {
|
||||||
const { activeTintColor, navigation, user, onItemPress } = this.props;
|
const { activeTintColor, balance, navigation, unclaimedRewardAmount, user, onItemPress } = this.props;
|
||||||
const { state } = navigation;
|
const { state } = navigation;
|
||||||
|
|
||||||
const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
|
const activeItemKey = state.routes[state.index] ? state.routes[state.index].key : null;
|
||||||
|
@ -144,7 +178,7 @@ class DrawerContent extends React.PureComponent {
|
||||||
const focused =
|
const focused =
|
||||||
activeItemKey === item.route ||
|
activeItemKey === item.route ||
|
||||||
(activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER &&
|
(activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER &&
|
||||||
item.route === Constants.DRAWER_ROUTE_DISCOVER) ||
|
item.route === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) ||
|
||||||
(activeItemKey === Constants.FULL_ROUTE_NAME_WALLET &&
|
(activeItemKey === Constants.FULL_ROUTE_NAME_WALLET &&
|
||||||
item.route === Constants.DRAWER_ROUTE_WALLET);
|
item.route === Constants.DRAWER_ROUTE_WALLET);
|
||||||
return (
|
return (
|
||||||
|
@ -156,7 +190,7 @@ class DrawerContent extends React.PureComponent {
|
||||||
focused ? discoverStyle.menuItemTouchAreaFocused : null,
|
focused ? discoverStyle.menuItemTouchAreaFocused : null,
|
||||||
]}
|
]}
|
||||||
key={item.label}
|
key={item.label}
|
||||||
onPress={() => navigation.navigate({ routeName: item.route })}
|
onPress={() => this.handleItemPress(item.route)}
|
||||||
delayPressIn={0}
|
delayPressIn={0}
|
||||||
>
|
>
|
||||||
<View style={discoverStyle.menuItemIcon}>
|
<View style={discoverStyle.menuItemIcon}>
|
||||||
|
@ -169,6 +203,15 @@ class DrawerContent extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
|
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
|
||||||
{__(item.label)}
|
{__(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>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,11 +11,6 @@ class FileDownloadButton extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
// this.checkAvailability(nextProps.uri);
|
|
||||||
// this.restartDownload(nextProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
restartDownload(props) {
|
restartDownload(props) {
|
||||||
const { downloading, fileInfo, uri, restartDownload } = props;
|
const { downloading, fileInfo, uri, restartDownload } = props;
|
||||||
|
|
||||||
|
@ -35,7 +30,6 @@ class FileDownloadButton extends React.PureComponent {
|
||||||
fileInfo,
|
fileInfo,
|
||||||
downloading,
|
downloading,
|
||||||
uri,
|
uri,
|
||||||
purchaseUri,
|
|
||||||
costInfo,
|
costInfo,
|
||||||
isPlayable,
|
isPlayable,
|
||||||
isViewable,
|
isViewable,
|
||||||
|
@ -45,10 +39,21 @@ class FileDownloadButton extends React.PureComponent {
|
||||||
doPause,
|
doPause,
|
||||||
style,
|
style,
|
||||||
openFile,
|
openFile,
|
||||||
|
onFileActionPress,
|
||||||
onButtonLayout,
|
onButtonLayout,
|
||||||
} = this.props;
|
} = 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,
|
const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0,
|
||||||
label = fileInfo ? __('%progress%% complete', { progress: progress.toFixed(0) }) : __('Connecting...');
|
label = fileInfo ? __('%progress%% complete', { progress: progress.toFixed(0) }) : __('Connecting...');
|
||||||
|
|
||||||
|
@ -72,31 +77,9 @@ class FileDownloadButton extends React.PureComponent {
|
||||||
text={isPlayable ? __('Play') : isViewable ? __('View') : __('Download')}
|
text={isPlayable ? __('Play') : isViewable ? __('View') : __('Download')}
|
||||||
onLayout={onButtonLayout}
|
onLayout={onButtonLayout}
|
||||||
style={[style, fileDownloadButtonStyle.container]}
|
style={[style, fileDownloadButtonStyle.container]}
|
||||||
onPress={() => {
|
onPress={onFileActionPress}
|
||||||
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();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} 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;
|
return null;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
makeSelectShortUrlForUri,
|
makeSelectShortUrlForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
import FileItem from './view';
|
import FileItem from './view';
|
||||||
|
@ -31,9 +32,7 @@ const select = (state, props) => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(FileItem);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(FileItem);
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ class FileItem extends React.PureComponent {
|
||||||
obscureNsfw,
|
obscureNsfw,
|
||||||
showDetails,
|
showDetails,
|
||||||
compactView,
|
compactView,
|
||||||
|
setPlayerVisible,
|
||||||
titleBeforeThumbnail,
|
titleBeforeThumbnail,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -69,7 +70,9 @@ class FileItem extends React.PureComponent {
|
||||||
const outpointsToHide = !blackListedOutpoints
|
const outpointsToHide = !blackListedOutpoints
|
||||||
? filteredOutpoints
|
? filteredOutpoints
|
||||||
: blackListedOutpoints.concat(filteredOutpoints);
|
: blackListedOutpoints.concat(filteredOutpoints);
|
||||||
shouldHide = outpointsToHide.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
|
shouldHide = outpointsToHide.some(
|
||||||
|
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (shouldHide) {
|
if (shouldHide) {
|
||||||
// don't display blacklisted or filtered outpoints on the Your tags page
|
// don't display blacklisted or filtered outpoints on the Your tags page
|
||||||
|
@ -107,7 +110,12 @@ class FileItem extends React.PureComponent {
|
||||||
<Icon style={discoverStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
|
<Icon style={discoverStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
|
||||||
)}
|
)}
|
||||||
{!compactView && (!fileInfo || !fileInfo.completed || !fileInfo.download_path) && (
|
{!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 && (
|
{!compactView && (
|
||||||
<View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
|
<View style={isRewardContent ? discoverStyle.rewardTitleContainer : null}>
|
||||||
|
@ -129,7 +137,8 @@ class FileItem extends React.PureComponent {
|
||||||
normalizeURI(shortChannelUri || fullChannelUri),
|
normalizeURI(shortChannelUri || fullChannelUri),
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
fullChannelUri
|
fullChannelUri,
|
||||||
|
setPlayerVisible,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -61,6 +61,7 @@ class FileItemMedia extends React.PureComponent {
|
||||||
let style = this.props.style;
|
let style = this.props.style;
|
||||||
const { duration, isResolvingUri, thumbnail, title, resizeMode } = this.props;
|
const { duration, isResolvingUri, thumbnail, title, resizeMode } = this.props;
|
||||||
const atStyle = this.state.autoThumbStyle;
|
const atStyle = this.state.autoThumbStyle;
|
||||||
|
const hasDuration = !!duration;
|
||||||
|
|
||||||
if (this.isThumbnailValid(thumbnail) && !this.state.imageLoadFailed) {
|
if (this.isThumbnailValid(thumbnail) && !this.state.imageLoadFailed) {
|
||||||
if (style == null) {
|
if (style == null) {
|
||||||
|
@ -75,7 +76,7 @@ class FileItemMedia extends React.PureComponent {
|
||||||
resizeMode={this.getFastImageResizeMode(resizeMode)}
|
resizeMode={this.getFastImageResizeMode(resizeMode)}
|
||||||
style={fileItemMediaStyle.image}
|
style={fileItemMediaStyle.image}
|
||||||
/>
|
/>
|
||||||
{duration && (
|
{duration > 0 && (
|
||||||
<VideoDuration
|
<VideoDuration
|
||||||
duration={duration}
|
duration={duration}
|
||||||
style={fileItemMediaStyle.duration}
|
style={fileItemMediaStyle.duration}
|
||||||
|
@ -104,7 +105,7 @@ class FileItemMedia extends React.PureComponent {
|
||||||
.toUpperCase()}
|
.toUpperCase()}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{duration && (
|
{duration > 0 && (
|
||||||
<VideoDuration
|
<VideoDuration
|
||||||
duration={duration}
|
duration={duration}
|
||||||
style={fileItemMediaStyle.duration}
|
style={fileItemMediaStyle.duration}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectThumbnailForUri,
|
makeSelectThumbnailForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints, selectRewardContentClaimIds } from 'lbryinc';
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
import FileListItem from './view';
|
import FileListItem from './view';
|
||||||
|
@ -32,9 +33,7 @@ const select = (state, props) => ({
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(FileListItem);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(FileListItem);
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { normalizeURI, parseURI } from 'lbry-redux';
|
import { normalizeURI, parseURI } from 'lbry-redux';
|
||||||
import { ActivityIndicator, Platform, Text, TouchableOpacity, View } from 'react-native';
|
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 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 Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import DateTime from 'component/dateTime';
|
import DateTime from 'component/dateTime';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
import FileItemMedia from 'component/fileItemMedia';
|
import FileItemMedia from 'component/fileItemMedia';
|
||||||
|
import FilePrice from 'component/filePrice';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
import NsfwOverlay from 'component/nsfwOverlay';
|
import NsfwOverlay from 'component/nsfwOverlay';
|
||||||
|
@ -14,27 +18,10 @@ import fileListStyle from 'styles/fileList';
|
||||||
|
|
||||||
class FileListItem extends React.PureComponent {
|
class FileListItem extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
|
autoStyle: null,
|
||||||
url: null,
|
url: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
getDownloadProgress = fileInfo => {
|
getDownloadProgress = fileInfo => {
|
||||||
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
|
return Math.ceil((fileInfo.written_bytes / fileInfo.total_bytes) * 100);
|
||||||
};
|
};
|
||||||
|
@ -44,11 +31,16 @@ class FileListItem extends React.PureComponent {
|
||||||
if (!claim && !batchResolve) {
|
if (!claim && !batchResolve) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
autoStyle:
|
||||||
|
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { claim, resolveUri, uri } = this.props;
|
const { claim, resolveUri, uri, batchResolve } = this.props;
|
||||||
if (!claim && uri !== this.state.url) {
|
if (!claim && uri !== this.state.url && !batchResolve) {
|
||||||
this.setState({ url: uri }, () => resolveUri(uri));
|
this.setState({ url: uri }, () => resolveUri(uri));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +80,7 @@ class FileListItem extends React.PureComponent {
|
||||||
onPress,
|
onPress,
|
||||||
navigation,
|
navigation,
|
||||||
rewardedContentClaimIds,
|
rewardedContentClaimIds,
|
||||||
|
setPlayerVisible,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
hideChannel,
|
hideChannel,
|
||||||
onLongPress,
|
onLongPress,
|
||||||
|
@ -106,9 +99,13 @@ class FileListItem extends React.PureComponent {
|
||||||
isRewardContent,
|
isRewardContent,
|
||||||
channelClaimId,
|
channelClaimId,
|
||||||
fullChannelUri,
|
fullChannelUri,
|
||||||
|
repostUrl,
|
||||||
|
repostChannel,
|
||||||
|
repostChannelUrl,
|
||||||
shortChannelUri,
|
shortChannelUri,
|
||||||
shouldHide,
|
shouldHide,
|
||||||
signingChannel;
|
signingChannel,
|
||||||
|
isRepost;
|
||||||
if (claim) {
|
if (claim) {
|
||||||
name = claim.name;
|
name = claim.name;
|
||||||
signingChannel = claim.signing_channel;
|
signingChannel = claim.signing_channel;
|
||||||
|
@ -118,12 +115,21 @@ class FileListItem extends React.PureComponent {
|
||||||
channelClaimId = signingChannel ? signingChannel.claim_id : null;
|
channelClaimId = signingChannel ? signingChannel.claim_id : null;
|
||||||
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
|
fullChannelUri = channelClaimId ? `${channel}#${channelClaimId}` : channel;
|
||||||
shortChannelUri = signingChannel ? signingChannel.short_url : null;
|
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) {
|
if (blackListedOutpoints || filteredOutpoints) {
|
||||||
const outpointsToHide = !blackListedOutpoints
|
const outpointsToHide = !blackListedOutpoints
|
||||||
? filteredOutpoints
|
? filteredOutpoints
|
||||||
: blackListedOutpoints.concat(filteredOutpoints);
|
: blackListedOutpoints.concat(filteredOutpoints);
|
||||||
shouldHide = outpointsToHide.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
|
shouldHide = outpointsToHide.some(
|
||||||
|
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: hide channels on tag pages?
|
// TODO: hide channels on tag pages?
|
||||||
|
@ -134,10 +140,35 @@ class FileListItem extends React.PureComponent {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const actualHideChannel = !isRepost && hideChannel;
|
||||||
|
const isChannel = name && name.startsWith('@');
|
||||||
|
const hasThumbnail = !!thumbnail;
|
||||||
return (
|
return (
|
||||||
<View style={style}>
|
<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
|
<TouchableOpacity
|
||||||
style={style}
|
style={[style, isChannel ? fileListStyle.channelContainer : null]}
|
||||||
onPress={this.onPressHandler}
|
onPress={this.onPressHandler}
|
||||||
onLongPress={() => {
|
onLongPress={() => {
|
||||||
if (onLongPress) {
|
if (onLongPress) {
|
||||||
|
@ -145,21 +176,55 @@ class FileListItem extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FileItemMedia
|
{!isChannel && (
|
||||||
style={fileListStyle.thumbnail}
|
<FileItemMedia
|
||||||
duration={duration}
|
style={fileListStyle.thumbnail}
|
||||||
resizeMode="cover"
|
duration={duration}
|
||||||
title={title || name || normalizeURI(uri).substring(7)}
|
resizeMode="cover"
|
||||||
thumbnail={thumbnail}
|
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 && (
|
{selected && (
|
||||||
<View style={fileListStyle.selectedOverlay}>
|
<View style={fileListStyle.selectedOverlay}>
|
||||||
<Icon name={'check-circle'} solid color={Colors.NextLbryGreen} size={32} />
|
<Icon name={'check-circle'} solid color={Colors.NextLbryGreen} size={32} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{fileInfo && fileInfo.completed && fileInfo.download_path && (
|
{fileInfo && fileInfo.completed && fileInfo.download_path && (
|
||||||
<Icon style={fileListStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
|
<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}>
|
<View style={fileListStyle.detailsContainer}>
|
||||||
{featuredResult && (
|
{featuredResult && (
|
||||||
<Text style={fileListStyle.featuredUri} numberOfLines={1}>
|
<Text style={fileListStyle.featuredUri} numberOfLines={1}>
|
||||||
|
@ -172,7 +237,7 @@ class FileListItem extends React.PureComponent {
|
||||||
{!title && !name && <Text style={fileListStyle.uri}>{uri}</Text>}
|
{!title && !name && <Text style={fileListStyle.uri}>{uri}</Text>}
|
||||||
{!title && !name && (
|
{!title && !name && (
|
||||||
<View style={fileListStyle.row}>
|
<View style={fileListStyle.row}>
|
||||||
<ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.LbryGreen} />
|
<ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.NextLbryGreen} />
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
@ -180,8 +245,11 @@ class FileListItem extends React.PureComponent {
|
||||||
|
|
||||||
{(title || name) && (
|
{(title || name) && (
|
||||||
<View style={fileListStyle.titleContainer}>
|
<View style={fileListStyle.titleContainer}>
|
||||||
<Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
|
<Text
|
||||||
{this.formatTitle(title) || this.formatTitle(name)}
|
style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}
|
||||||
|
numberOfLines={actualHideChannel ? 4 : 3}
|
||||||
|
>
|
||||||
|
{title || name}
|
||||||
</Text>
|
</Text>
|
||||||
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
|
{isRewardContent && <Icon style={fileListStyle.rewardIcon} name="award" size={12} />}
|
||||||
</View>
|
</View>
|
||||||
|
@ -193,17 +261,18 @@ class FileListItem extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{channel && !hideChannel && (
|
{(channel || isChannel) && !actualHideChannel && (
|
||||||
<Link
|
<Link
|
||||||
style={fileListStyle.publisher}
|
style={fileListStyle.publisher}
|
||||||
text={channel}
|
text={isChannel ? name : channel}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigateToUri(
|
navigateToUri(
|
||||||
navigation,
|
navigation,
|
||||||
normalizeURI(shortChannelUri || fullChannelUri),
|
normalizeURI(isChannel ? uri : shortChannelUri || fullChannelUri),
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
fullChannelUri
|
isChannel ? claim && claim.permanent_url : fullChannelUri,
|
||||||
|
setPlayerVisible,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -211,7 +280,7 @@ class FileListItem extends React.PureComponent {
|
||||||
|
|
||||||
<View style={fileListStyle.info}>
|
<View style={fileListStyle.info}>
|
||||||
{fileInfo && !isNaN(fileInfo.written_bytes) && fileInfo.written_bytes > 0 && (
|
{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} />
|
<DateTime style={fileListStyle.publishInfo} textStyle={fileListStyle.infoText} timeAgo uri={uri} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -224,7 +293,7 @@ class FileListItem extends React.PureComponent {
|
||||||
color={Colors.NextLbryGreen}
|
color={Colors.NextLbryGreen}
|
||||||
height={3}
|
height={3}
|
||||||
style={fileListStyle.progress}
|
style={fileListStyle.progress}
|
||||||
progress={this.getDownloadProgress(fileInfo)}
|
progress={getDownloadProgress(fileInfo)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import { formatCredits, formatFullPrice } from 'lbry-redux';
|
import { formatCredits, formatFullPrice } from 'lbry-redux';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
|
||||||
class CreditAmount extends React.PureComponent {
|
class CreditAmount extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -62,25 +63,27 @@ class CreditAmount extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* {this.props.isEstimate ? (
|
return (
|
||||||
<span
|
<Text style={style} numberOfLines={1}>
|
||||||
className="credit-amount__estimate"
|
{amountText}
|
||||||
title={__('This is an estimate and does not include data fees')}
|
</Text>
|
||||||
>
|
);
|
||||||
*
|
|
||||||
</span>
|
|
||||||
) : null} */
|
|
||||||
return <Text style={style}>{amountText}</Text>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilePrice extends React.PureComponent {
|
class FilePrice extends React.PureComponent {
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.fetchCost(this.props);
|
const { cost } = this.props;
|
||||||
|
if (isNaN(parseFloat(cost))) {
|
||||||
|
this.fetchCost(this.props);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
this.fetchCost(nextProps);
|
const { cost } = this.props;
|
||||||
|
if (isNaN(parseFloat(cost))) {
|
||||||
|
this.fetchCost(nextProps);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchCost(props) {
|
fetchCost(props) {
|
||||||
|
@ -92,30 +95,25 @@ class FilePrice extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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;
|
const isEstimate = costInfo ? !costInfo.includesData : null;
|
||||||
|
const amount = cost ? parseFloat(cost) : costInfo ? parseFloat(costInfo.cost) : 0;
|
||||||
if (!costInfo) {
|
if (isNaN(amount) || amount === 0) {
|
||||||
return (
|
return null;
|
||||||
<View style={style}>
|
|
||||||
<Text style={textStyle}>???</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={style}>
|
<View style={style}>
|
||||||
|
<Icon name="coins" size={9} style={iconStyle} />
|
||||||
<CreditAmount
|
<CreditAmount
|
||||||
style={textStyle}
|
style={textStyle}
|
||||||
label={false}
|
label={false}
|
||||||
amount={parseFloat(costInfo.cost)}
|
amount={amount}
|
||||||
isEstimate={isEstimate}
|
isEstimate={isEstimate}
|
||||||
showFree
|
showFree
|
||||||
showFullPrice={showFullPrice}
|
showFullPrice={showFullPrice}
|
||||||
>
|
/>
|
||||||
???
|
|
||||||
</CreditAmount>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { selectBalance } from 'lbry-redux';
|
import { selectBalance } from 'lbry-redux';
|
||||||
import { selectUnclaimedRewardValue } from 'lbryinc';
|
import { selectUnclaimedRewardValue } from 'lbryinc';
|
||||||
import Constants from 'constants';
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import FloatingWalletBalance from './view';
|
import FloatingWalletBalance from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -11,7 +11,4 @@ const select = state => ({
|
||||||
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
|
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, null)(FloatingWalletBalance);
|
||||||
select,
|
|
||||||
null
|
|
||||||
)(FloatingWalletBalance);
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
buffering: false,
|
buffering: true,
|
||||||
backgroundPlayEnabled: false,
|
backgroundPlayEnabled: false,
|
||||||
autoPaused: false,
|
autoPaused: false,
|
||||||
rate: 1,
|
rate: 1,
|
||||||
|
@ -45,9 +45,9 @@ class MediaPlayer extends React.PureComponent {
|
||||||
resizeMode: 'contain',
|
resizeMode: 'contain',
|
||||||
duration: 0.0,
|
duration: 0.0,
|
||||||
currentTime: 0.0,
|
currentTime: 0.0,
|
||||||
paused: !props.autoPlay,
|
paused: true,
|
||||||
fullscreenMode: false,
|
fullscreenMode: false,
|
||||||
areControlsVisible: true,
|
areControlsVisible: false,
|
||||||
controlsTimeout: -1,
|
controlsTimeout: -1,
|
||||||
seekerOffset: 0,
|
seekerOffset: 0,
|
||||||
seekerPosition: 0,
|
seekerPosition: 0,
|
||||||
|
@ -87,7 +87,9 @@ class MediaPlayer extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad = data => {
|
onLoad = data => {
|
||||||
|
const { autoPlay } = this.props;
|
||||||
this.setState({
|
this.setState({
|
||||||
|
buffering: false,
|
||||||
duration: data.duration,
|
duration: data.duration,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,13 +102,17 @@ class MediaPlayer extends React.PureComponent {
|
||||||
if (this.props.onMediaLoaded) {
|
if (this.props.onMediaLoaded) {
|
||||||
this.props.onMediaLoaded();
|
this.props.onMediaLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (autoPlay) {
|
||||||
|
this.setState({ paused: false });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onProgress = data => {
|
onProgress = data => {
|
||||||
const { savePosition, claim } = this.props;
|
const { savePosition, claim } = this.props;
|
||||||
|
|
||||||
this.setState({ buffering: false, currentTime: data.currentTime });
|
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;
|
const { claim_id: claimId, txid, nout } = claim;
|
||||||
savePosition(claimId, `${txid}:${nout}`, data.currentTime);
|
savePosition(claimId, `${txid}:${nout}`, data.currentTime);
|
||||||
}
|
}
|
||||||
|
@ -167,7 +173,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
this.showPlayerControls();
|
this.showPlayerControls();
|
||||||
this.setState({ paused: !this.state.paused }, this.handlePausedState);
|
this.setState({ paused: !this.state.paused }, this.updateBackgroundMediaNotification);
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePausedState = () => {
|
handlePausedState = () => {
|
||||||
|
@ -188,7 +194,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
onEnd = () => {
|
onEnd = () => {
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
|
||||||
if (this.props.onPlaybackFinished) {
|
if (this.props.onPlaybackFinished) {
|
||||||
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 = () => {
|
onBuffer = () => {
|
||||||
if (!this.state.paused) {
|
if (!this.state.paused) {
|
||||||
this.setState({ buffering: true }, () => this.manualHidePlayerControls());
|
this.setState({ buffering: true }, () => this.manualHidePlayerControls());
|
||||||
|
@ -341,6 +351,12 @@ class MediaPlayer extends React.PureComponent {
|
||||||
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
|
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleSeek = time => {
|
||||||
|
const { currentTime } = this.state;
|
||||||
|
const newTime = currentTime + time;
|
||||||
|
this.seekTo(newTime);
|
||||||
|
};
|
||||||
|
|
||||||
updateBackgroundMediaNotification = () => {
|
updateBackgroundMediaNotification = () => {
|
||||||
this.handlePausedState();
|
this.handlePausedState();
|
||||||
const { backgroundPlayEnabled } = this.props;
|
const { backgroundPlayEnabled } = this.props;
|
||||||
|
@ -362,14 +378,26 @@ class MediaPlayer extends React.PureComponent {
|
||||||
<Icon name={'arrow-left'} size={18} style={mediaPlayerStyle.backButtonIcon} />
|
<Icon name={'arrow-left'} size={18} style={mediaPlayerStyle.backButtonIcon} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity style={mediaPlayerStyle.playPauseButton} onPress={this.togglePlay}>
|
<View style={mediaPlayerStyle.midControls}>
|
||||||
{this.state.paused && <Icon name="play" size={40} color="#ffffff" />}
|
<TouchableOpacity style={mediaPlayerStyle.rewind10} onPress={() => this.handleSeek(-10)}>
|
||||||
{!this.state.paused && <Icon name="pause" size={40} color="#ffffff" />}
|
<Icon name="undo" size={24} color={Colors.White} />
|
||||||
</TouchableOpacity>
|
<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}>
|
<TouchableOpacity style={mediaPlayerStyle.toggleFullscreenButton} onPress={this.toggleFullscreenMode}>
|
||||||
{this.state.fullscreenMode && <Icon name="compress" size={16} color="#ffffff" />}
|
{this.state.fullscreenMode && <Icon name="compress" size={16} color={Colors.White} />}
|
||||||
{!this.state.fullscreenMode && <Icon name="expand" size={16} color="#ffffff" />}
|
{!this.state.fullscreenMode && <Icon name="expand" size={16} color={Colors.White} />}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<Text style={mediaPlayerStyle.elapsedDuration}>{this.formatTime(this.state.currentTime)}</Text>
|
<Text style={mediaPlayerStyle.elapsedDuration}>{this.formatTime(this.state.currentTime)}</Text>
|
||||||
|
@ -421,7 +449,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
const seekerCircleStyle = [this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle];
|
const seekerCircleStyle = [this.state.seeking ? mediaPlayerStyle.bigSeekerCircle : mediaPlayerStyle.seekerCircle];
|
||||||
if (!this.state.seeking) {
|
if (!this.state.seeking) {
|
||||||
seekerCircleStyle.push(
|
seekerCircleStyle.push(
|
||||||
this.state.fullscreenMode ? mediaPlayerStyle.seekerCircleTopFs : mediaPlayerStyle.seekerCircleTop
|
this.state.fullscreenMode ? mediaPlayerStyle.seekerCircleTopFs : mediaPlayerStyle.seekerCircleTop,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -440,6 +468,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
}}
|
}}
|
||||||
resizeMode={this.state.resizeMode}
|
resizeMode={this.state.resizeMode}
|
||||||
playInBackground={this.state.backgroundPlayEnabled}
|
playInBackground={this.state.backgroundPlayEnabled}
|
||||||
|
playWhenInactive={this.state.backgroundPlayEnabled}
|
||||||
style={mediaPlayerStyle.player}
|
style={mediaPlayerStyle.player}
|
||||||
rate={this.state.rate}
|
rate={this.state.rate}
|
||||||
volume={this.state.volume}
|
volume={this.state.volume}
|
||||||
|
@ -450,6 +479,7 @@ class MediaPlayer extends React.PureComponent {
|
||||||
onEnd={this.onEnd}
|
onEnd={this.onEnd}
|
||||||
onError={this.onError}
|
onError={this.onError}
|
||||||
minLoadRetryCount={999}
|
minLoadRetryCount={999}
|
||||||
|
onAudioFocusChanged={this.onFocusChanged}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{this.state.firstPlay && thumbnail && (
|
{this.state.firstPlay && thumbnail && (
|
||||||
|
|
28
src/component/modalRepostView/index.js
Normal file
28
src/component/modalRepostView/index.js
Normal 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);
|
204
src/component/modalRepostView/view.js
Normal file
204
src/component/modalRepostView/view.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { selectFetchingClaimSearch } from 'lbry-redux';
|
||||||
import ModalSuggestedSubscriptions from './view';
|
import ModalSuggestedSubscriptions from './view';
|
||||||
|
|
||||||
export default connect()(ModalSuggestedSubscriptions);
|
const select = state => ({
|
||||||
|
loadingSuggested: selectFetchingClaimSearch(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select)(ModalSuggestedSubscriptions);
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
import { ActivityIndicator, ScrollView, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import modalStyle from 'styles/modal';
|
import modalStyle from 'styles/modal';
|
||||||
import subscriptionsStyle from 'styles/subscriptions';
|
import subscriptionsStyle from 'styles/subscriptions';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import SuggestedSubscriptions from 'component/suggestedSubscriptions';
|
import SuggestedSubscriptionsGrid from 'component/suggestedSubscriptionsGrid';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
|
||||||
export default class ModalSuggestedSubcriptions extends React.PureComponent {
|
export default class ModalSuggestedSubcriptions extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { navigation, onDonePress, onOverlayPress } = this.props;
|
const { loadingSuggested, navigation, onDonePress, onOverlayPress } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
|
<TouchableOpacity style={modalStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
|
||||||
<TouchableOpacity style={[modalStyle.container, subscriptionsStyle.modalContainer]} activeOpacity={1}>
|
<TouchableOpacity style={[modalStyle.container, subscriptionsStyle.modalContainer]} activeOpacity={1}>
|
||||||
<SuggestedSubscriptions inModal navigation={navigation} />
|
<SuggestedSubscriptionsGrid inModal navigation={navigation} />
|
||||||
<View style={modalStyle.buttons}>
|
<View style={modalStyle.wideButtons}>
|
||||||
<Button style={modalStyle.doneButton} text={__('Done')} onPress={onDonePress} />
|
<Button style={modalStyle.wideDoneButton} text={__('Done')} onPress={onDonePress} />
|
||||||
|
{loadingSuggested && (
|
||||||
|
<ActivityIndicator size="small" color={Colors.White} style={subscriptionsStyle.modalLoading} />
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
17
src/component/modalTipView/index.js
Normal file
17
src/component/modalTipView/index.js
Normal 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);
|
157
src/component/modalTipView/view.js
Normal file
157
src/component/modalTipView/view.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,35 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
doResolveUris,
|
doResolveUris,
|
||||||
|
doResolvedSearch,
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectRecommendedContentForUri,
|
makeSelectResolvedRecommendedContentForUri,
|
||||||
makeSelectTitleForUri,
|
selectResolvingUris,
|
||||||
selectIsSearching,
|
selectIsSearching,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
import RelatedContent from './view';
|
import RelatedContent from './view';
|
||||||
|
|
||||||
|
const RESULT_SIZE = 16;
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
recommendedContent: makeSelectRecommendedContentForUri(props.uri)(state),
|
|
||||||
title: makeSelectTitleForUri(props.uri)(state),
|
|
||||||
isSearching: selectIsSearching(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 => ({
|
const perform = dispatch => ({
|
||||||
resolveUris: uris => dispatch(doResolveUris(uris)),
|
resolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
|
searchRecommended: (query, claimId, nsfw) =>
|
||||||
|
dispatch(doResolvedSearch(query, RESULT_SIZE, undefined, true, { related_to: claimId }, nsfw)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(RelatedContent);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(RelatedContent);
|
|
||||||
|
|
|
@ -4,44 +4,42 @@ import { normalizeURI } from 'lbry-redux';
|
||||||
import { navigateToUri } from 'utils/helper';
|
import { navigateToUri } from 'utils/helper';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import FileListItem from 'component/fileListItem';
|
import FileListItem from 'component/fileListItem';
|
||||||
|
import ClaimResultItem from 'component/claimResultItem';
|
||||||
import fileListStyle from 'styles/fileList';
|
import fileListStyle from 'styles/fileList';
|
||||||
import relatedContentStyle from 'styles/relatedContent';
|
import relatedContentStyle from 'styles/relatedContent';
|
||||||
|
|
||||||
export default class RelatedContent extends React.PureComponent {
|
export default class RelatedContent extends React.PureComponent {
|
||||||
state = {
|
componentDidMount() {
|
||||||
urlsResolved: false,
|
const { title, claimId, searchRecommended, showNsfwContent } = this.props;
|
||||||
};
|
if (title && claimId) {
|
||||||
|
searchRecommended(title, claimId, showNsfwContent);
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { resolveUris, recommendedContent } = this.props;
|
|
||||||
|
|
||||||
if (recommendedContent && recommendedContent.length > 0 && !this.state.urisResolved) {
|
|
||||||
this.setState({ urisResolved: true }, () => {
|
|
||||||
// batch resolve the uris
|
|
||||||
resolveUris(recommendedContent);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
const { isSearching, recommendedContent } = nextProps;
|
||||||
|
return isSearching || (!isSearching && recommendedContent && recommendedContent.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { recommendedContent, navigation, uri, fullUri } = this.props;
|
const { isSearching, recommendedContent, navigation, urlOpenHandler, uri, fullUri } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={relatedContentStyle.container}>
|
<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 &&
|
||||||
recommendedContent
|
recommendedContent.map(result => (
|
||||||
.filter(recommendedUri => recommendedUri !== normalizeURI(fullUri))
|
<ClaimResultItem
|
||||||
.map(recommendedUri => (
|
style={fileListStyle.item}
|
||||||
<FileListItem
|
uri={result ? normalizeURI(`${result.name}#${result.claimId}`) : null}
|
||||||
style={fileListStyle.item}
|
urlOpenHandler={urlOpenHandler}
|
||||||
key={recommendedUri}
|
key={result.claimId}
|
||||||
uri={recommendedUri}
|
result={result}
|
||||||
batchResolve
|
navigation={navigation}
|
||||||
navigation={navigation}
|
autoplay
|
||||||
autoplay
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native';
|
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 Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import Link from '../link';
|
import Link from 'component/link';
|
||||||
import rewardStyle from '../../styles/reward';
|
import rewardStyle from 'styles/reward';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
canClaim: boolean,
|
canClaim: boolean,
|
||||||
|
@ -61,7 +62,7 @@ class RewardCard extends React.PureComponent<Props> {
|
||||||
if (reward) {
|
if (reward) {
|
||||||
const claimed = !!reward.transaction_id;
|
const claimed = !!reward.transaction_id;
|
||||||
if (!claimed && reward.reward_range && reward.reward_range.includes('-')) {
|
if (!claimed && reward.reward_range && reward.reward_range.includes('-')) {
|
||||||
return reward.reward_range.split('-')[0] + '+'; // ex: 5+
|
return reward.reward_range.split('-')[1];
|
||||||
} else if (reward.reward_amount > 0) {
|
} else if (reward.reward_amount > 0) {
|
||||||
return reward.reward_amount;
|
return reward.reward_amount;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ class RewardCard extends React.PureComponent<Props> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { canClaim, isPending, onClaimPress, reward } = this.props;
|
const { canClaim, isPending, onClaimPress, reward, usdExchangeRate } = this.props;
|
||||||
const claimed = !!reward.transaction_id;
|
const claimed = !!reward.transaction_id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -117,8 +118,16 @@ class RewardCard extends React.PureComponent<Props> {
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<View style={rewardStyle.rightCol}>
|
<View style={rewardStyle.rightCol}>
|
||||||
|
{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.rewardAmount}>{this.getDisplayAmount()}</Text>
|
||||||
<Text style={rewardStyle.rewardCurrency}>LBC</Text>
|
<Text style={rewardStyle.rewardCurrency}>LBC</Text>
|
||||||
|
{usdExchangeRate > 0 && (
|
||||||
|
<Text style={rewardStyle.rewardUsd}>
|
||||||
|
≈{formatUsd(parseFloat(this.getDisplayAmount()) * parseFloat(usdExchangeRate))}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Link from 'component/link';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import rewardStyle from 'styles/reward';
|
import rewardStyle from 'styles/reward';
|
||||||
|
import { formatUsd } from '../../utils/helper';
|
||||||
|
|
||||||
class RewardEnrolment extends React.Component {
|
class RewardEnrolment extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -29,7 +30,7 @@ class RewardEnrolment extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fetching, navigation, unclaimedRewardAmount, user } = this.props;
|
const { unclaimedRewardAmount, usdExchangeRate } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={rewardStyle.enrollContainer}>
|
<View style={rewardStyle.enrollContainer}>
|
||||||
|
@ -43,9 +44,11 @@ class RewardEnrolment extends React.Component {
|
||||||
|
|
||||||
<View style={rewardStyle.onboarding}>
|
<View style={rewardStyle.onboarding}>
|
||||||
<Text style={rewardStyle.enrollDescText}>
|
<Text style={rewardStyle.enrollDescText}>
|
||||||
{__('LBRY credits allow you to purchase content, publish content, and influence the network.')}
|
{__('LBRY credits allow you to publish or purchase content.')}
|
||||||
{'\n\n'}
|
{'\n\n'}
|
||||||
{__('You get credits for free for providing an email address and taking other basic actions.')}
|
{__('You can obtain free credits worth %amount% after you provide an email address.', {
|
||||||
|
amount: formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(usdExchangeRate)),
|
||||||
|
})}
|
||||||
{'\n\n'}
|
{'\n\n'}
|
||||||
<Link style={rewardStyle.learnMoreLink} text={__('Learn more')} onPress={this.onLearnMorePressed} />.
|
<Link style={rewardStyle.learnMoreLink} text={__('Learn more')} onPress={this.onLearnMorePressed} />.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
4
src/component/sdkLoadingStatus/index.js
Normal file
4
src/component/sdkLoadingStatus/index.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import SdkLoadingStatus from './view';
|
||||||
|
|
||||||
|
export default connect()(SdkLoadingStatus);
|
15
src/component/sdkLoadingStatus/view.js
Normal file
15
src/component/sdkLoadingStatus/view.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
18
src/component/subscribeButtonOverlay/index.js
Normal file
18
src/component/subscribeButtonOverlay/index.js
Normal 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);
|
36
src/component/subscribeButtonOverlay/view.js
Normal file
36
src/component/subscribeButtonOverlay/view.js
Normal 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;
|
|
@ -6,6 +6,7 @@ import {
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
makeSelectIsUriResolving,
|
makeSelectIsUriResolving,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { doChannelSubscribe, doChannelUnsubscribe, makeSelectIsSubscribed } from 'lbryinc';
|
||||||
import SuggestedSubscriptionItem from './view';
|
import SuggestedSubscriptionItem from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -13,13 +14,13 @@ const select = (state, props) => ({
|
||||||
title: makeSelectTitleForUri(props.uri)(state),
|
title: makeSelectTitleForUri(props.uri)(state),
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||||
|
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
|
subscribe: subscription => doChannelSubscribe(subscription),
|
||||||
|
unsubscribe: subscription => doChannelUnsubscribe(subscription),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(SuggestedSubscriptionItem);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(SuggestedSubscriptionItem);
|
|
||||||
|
|
|
@ -1,25 +1,37 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { buildURI, normalizeURI } from 'lbry-redux';
|
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 { navigateToUri } from 'utils/helper';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
|
import ChannelIconItem from 'component/channelIconItem';
|
||||||
|
import channelIconStyle from 'styles/channelIcon';
|
||||||
import discoverStyle from 'styles/discover';
|
import discoverStyle from 'styles/discover';
|
||||||
import FileItem from 'component/fileItem';
|
import FileItem from 'component/fileItem';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButtonOverlay from 'component/subscribeButtonOverlay';
|
||||||
import subscriptionsStyle from 'styles/subscriptions';
|
import subscriptionsStyle from 'styles/subscriptions';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
|
|
||||||
class SuggestedSubscriptionItem extends React.PureComponent {
|
class SuggestedSubscriptionItem extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
autoStyle: null,
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { claim, uri, resolveUri } = this.props;
|
const { claim, uri, resolveUri } = this.props;
|
||||||
if (!claim) {
|
if (!claim) {
|
||||||
resolveUri(uri);
|
resolveUri(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
autoStyle:
|
||||||
|
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { claim, isResolvingUri, navigation, thumbnail, title, uri } = this.props;
|
const { claim, isResolvingUri, navigation, thumbnail, title, uri } = this.props;
|
||||||
|
|
||||||
let shortUrl, tags;
|
let shortUrl, tags;
|
||||||
if (claim) {
|
if (claim) {
|
||||||
shortUrl = claim.short_url;
|
shortUrl = claim.short_url;
|
||||||
|
@ -28,54 +40,60 @@ class SuggestedSubscriptionItem extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasThumbnail = !!thumbnail;
|
||||||
if (isResolvingUri) {
|
if (isResolvingUri) {
|
||||||
return (
|
return (
|
||||||
<View style={subscriptionsStyle.itemLoadingContainer}>
|
<View style={subscriptionsStyle.itemLoadingContainer}>
|
||||||
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={subscriptionsStyle.suggestedItem}>
|
<TouchableOpacity style={subscriptionsStyle.suggestedItem}>
|
||||||
<View style={subscriptionsStyle.suggestedItemThumbnailContainer}>
|
<View style={[subscriptionsStyle.suggestedItemThumbnailContainer, this.state.autoStyle]}>
|
||||||
<Image
|
{hasThumbnail && (
|
||||||
style={subscriptionsStyle.suggestedItemThumbnail}
|
<Image style={subscriptionsStyle.suggestedItemThumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} />
|
||||||
resizeMode={'cover'}
|
)}
|
||||||
source={thumbnail ? { uri: thumbnail } : require('../../assets/default_avatar.jpg')}
|
{!hasThumbnail && (
|
||||||
/>
|
<Text style={channelIconStyle.autothumbCharacter}>
|
||||||
|
{title ? title.substring(0, 1).toUpperCase() : claim ? claim.name.substring(1, 2).toUpperCase() : ''}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={subscriptionsStyle.suggestedItemDetails}>
|
<View style={subscriptionsStyle.suggestedItemDetails}>
|
||||||
{title && (
|
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={2}>
|
||||||
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={1}>
|
{title || claim.name}
|
||||||
{title}
|
</Text>
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{claim && (
|
|
||||||
<Link
|
|
||||||
style={subscriptionsStyle.suggestedItemName}
|
|
||||||
numberOfLines={1}
|
|
||||||
text={claim.name}
|
|
||||||
onPress={() => navigateToUri(navigation, normalizeURI(shortUrl || uri), null, false, claim.permanent_url)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{tags && (
|
{tags && (
|
||||||
<View style={subscriptionsStyle.suggestedItemTagList}>
|
<View style={subscriptionsStyle.suggestedItemTagList}>
|
||||||
{tags &&
|
{tags &&
|
||||||
tags
|
tags
|
||||||
.slice(0, 3)
|
.slice(0, 1)
|
||||||
.map(tag => (
|
.map(tag => (
|
||||||
<Tag style={subscriptionsStyle.tag} key={tag} name={tag} navigation={navigation} truncate />
|
<Tag
|
||||||
|
numberOfLines={1}
|
||||||
|
onPress={this.handleItemPress}
|
||||||
|
style={subscriptionsStyle.tag}
|
||||||
|
key={tag}
|
||||||
|
name={tag}
|
||||||
|
navigation={navigation}
|
||||||
|
truncate
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{claim && (
|
{claim && (
|
||||||
<SubscribeButton style={subscriptionsStyle.suggestedItemSubscribe} uri={normalizeURI(claim.permanent_url)} />
|
<SubscribeButtonOverlay
|
||||||
|
claim={claim}
|
||||||
|
style={subscriptionsStyle.suggestedItemSubscribeOverlay}
|
||||||
|
uri={normalizeURI(claim.permanent_url)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,4 @@ const perform = dispatch => ({
|
||||||
claimSearch: options => dispatch(doClaimSearch(options)),
|
claimSearch: options => dispatch(doClaimSearch(options)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(SuggestedSubscriptions);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(SuggestedSubscriptions);
|
|
||||||
|
|
27
src/component/suggestedSubscriptionsGrid/index.js
Normal file
27
src/component/suggestedSubscriptionsGrid/index.js
Normal 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);
|
111
src/component/suggestedSubscriptionsGrid/view.js
Normal file
111
src/component/suggestedSubscriptionsGrid/view.js
Normal 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;
|
|
@ -30,7 +30,7 @@ export default class Tag extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, onPress, style, type, truncate } = this.props;
|
const { name, numberOfLines, onPress, style, type, truncate } = this.props;
|
||||||
|
|
||||||
let styles = [];
|
let styles = [];
|
||||||
if (style) {
|
if (style) {
|
||||||
|
@ -50,7 +50,9 @@ export default class Tag extends React.PureComponent {
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
|
<TouchableOpacity style={styles} onPress={onPress || this.onPressDefault}>
|
||||||
<View style={tagStyle.content}>
|
<View style={tagStyle.content}>
|
||||||
<Text style={tagStyle.text}>{truncate ? formatTagName(name) : 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} />}
|
{type && <Icon style={tagStyle.icon} name={type === 'add' ? 'plus' : 'times'} size={8} />}
|
||||||
</View>
|
</View>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
selectSearchSuggestions,
|
selectSearchSuggestions,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import UriBar from './view';
|
import UriBar from './view';
|
||||||
|
@ -24,9 +25,10 @@ const select = state => {
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
|
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
|
||||||
|
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
perform,
|
||||||
)(UriBar);
|
)(UriBar);
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SEARCH_TYPES, isNameValid, isURIValid, normalizeURI } from 'lbry-redux';
|
import { SEARCH_TYPES, isNameValid, isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
import { Dimensions, FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
import { Alert, Dimensions, FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||||
import { navigateToUri, transformUrl } from 'utils/helper';
|
import { navigateToUri, transformUrl } from 'utils/helper';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import UriBarItem from './internal/uri-bar-item';
|
import UriBarItem from './internal/uri-bar-item';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import NavigationButton from 'component/navigationButton';
|
import NavigationButton from 'component/navigationButton';
|
||||||
import uriBarStyle from 'styles/uriBar';
|
import uriBarStyle from 'styles/uriBar';
|
||||||
|
import { NavigationActions, StackActions } from 'react-navigation';
|
||||||
|
|
||||||
class UriBar extends React.PureComponent {
|
class UriBar extends React.PureComponent {
|
||||||
static INPUT_TIMEOUT = 2500; // 2.5 seconds
|
static INPUT_TIMEOUT = 2500; // 2.5 seconds
|
||||||
|
@ -18,18 +19,20 @@ class UriBar extends React.PureComponent {
|
||||||
|
|
||||||
keyboardDidHideListener = null;
|
keyboardDidHideListener = null;
|
||||||
|
|
||||||
|
changeTextTimeout = -1;
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
changeTextTimeout: null,
|
|
||||||
currentValue: null,
|
currentValue: null,
|
||||||
inputText: null,
|
inputText: null,
|
||||||
focused: false,
|
focused: false,
|
||||||
keyboardHeight: 0,
|
keyboardHeight: 0,
|
||||||
|
selection: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
|
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
|
||||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
||||||
this.setSelection();
|
this.setState({ selection: { start: 0, end: 0 } });
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -51,15 +54,14 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeText = text => {
|
handleChangeText = text => {
|
||||||
|
this.setState({ selection: undefined });
|
||||||
const newValue = text || '';
|
const newValue = text || '';
|
||||||
clearTimeout(this.state.changeTextTimeout);
|
clearTimeout(this.changeTextTimeout);
|
||||||
const { updateSearchQuery, onSearchSubmitted, showUriBarSuggestions, navigation } = this.props;
|
const { updateSearchQuery, onSearchSubmitted, showUriBarSuggestions, navigation } = this.props;
|
||||||
|
|
||||||
updateSearchQuery(text);
|
this.changeTextTimeout = -1;
|
||||||
|
|
||||||
let timeout = -1;
|
|
||||||
if (!showUriBarSuggestions) {
|
if (!showUriBarSuggestions) {
|
||||||
timeout = setTimeout(() => {
|
this.changeTextTimeout = setTimeout(() => {
|
||||||
if (text.trim().length === 0) {
|
if (text.trim().length === 0) {
|
||||||
// don't do anything if the text is empty
|
// don't do anything if the text is empty
|
||||||
return;
|
return;
|
||||||
|
@ -67,6 +69,7 @@ class UriBar extends React.PureComponent {
|
||||||
|
|
||||||
if (!text.startsWith('lbry://')) {
|
if (!text.startsWith('lbry://')) {
|
||||||
// not a URI input, so this is a search, perform a direct search
|
// not a URI input, so this is a search, perform a direct search
|
||||||
|
updateSearchQuery(text);
|
||||||
if (onSearchSubmitted) {
|
if (onSearchSubmitted) {
|
||||||
onSearchSubmitted(text);
|
onSearchSubmitted(text);
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,12 +77,14 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, UriBar.INPUT_TIMEOUT);
|
}, UriBar.INPUT_TIMEOUT);
|
||||||
|
} else {
|
||||||
|
updateSearchQuery(text);
|
||||||
}
|
}
|
||||||
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
|
this.setState({ inputText: newValue, currentValue: newValue });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleItemPress = item => {
|
handleItemPress = item => {
|
||||||
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
|
const { navigation, onSearchSubmitted, setPlayerVisible, updateSearchQuery } = this.props;
|
||||||
const { type, value } = item;
|
const { type, value } = item;
|
||||||
|
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
|
@ -108,7 +113,7 @@ class UriBar extends React.PureComponent {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const uri = normalizeURI(value);
|
const uri = normalizeURI(value);
|
||||||
navigateToUri(navigation, uri);
|
navigateToUri(navigation, uri, null, false, null, setPlayerVisible);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,14 +128,8 @@ class UriBar extends React.PureComponent {
|
||||||
this.setState({ focused: false, keyboardHeight: 0 });
|
this.setState({ focused: false, keyboardHeight: 0 });
|
||||||
};
|
};
|
||||||
|
|
||||||
setSelection() {
|
|
||||||
if (this.textInput) {
|
|
||||||
this.textInput.setNativeProps({ selection: { start: 0, end: 0 } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmitEditing = () => {
|
handleSubmitEditing = () => {
|
||||||
const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
|
const { navigation, onSearchSubmitted, setPlayerVisible, updateSearchQuery } = this.props;
|
||||||
if (this.state.inputText) {
|
if (this.state.inputText) {
|
||||||
let inputText = this.state.inputText,
|
let inputText = this.state.inputText,
|
||||||
inputTextIsUrl = false;
|
inputTextIsUrl = false;
|
||||||
|
@ -139,7 +138,7 @@ class UriBar extends React.PureComponent {
|
||||||
// if it's a URI (lbry://...), open the file page
|
// if it's a URI (lbry://...), open the file page
|
||||||
if (transformedUrl && isURIValid(transformedUrl)) {
|
if (transformedUrl && isURIValid(transformedUrl)) {
|
||||||
inputTextIsUrl = true;
|
inputTextIsUrl = true;
|
||||||
navigateToUri(navigation, transformedUrl);
|
navigateToUri(navigation, transformedUrl, null, false, null, setPlayerVisible);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,9 +158,58 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSearchPageBlurred() {
|
handleFocus = () => {
|
||||||
this.setState({ currenValueSet: false });
|
this.setState(
|
||||||
}
|
{
|
||||||
|
focused: true,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.setState({
|
||||||
|
selection: { start: 0, end: this.state.currentValue ? this.state.currentValue.length : 0 },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTouchStart = () => {
|
||||||
|
if (this.state.focused) {
|
||||||
|
this.setState({ selection: null });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleBlur = () => {
|
||||||
|
this.setState({
|
||||||
|
focused: false,
|
||||||
|
selection: { start: 0, end: 0 },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleNavigationButtonPress = () => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
if (!navigation.openDrawer) {
|
||||||
|
Alert.alert(
|
||||||
|
__('Stop watching?'),
|
||||||
|
'The LBRY service is still loading stuff in the background. Would you like to continue?',
|
||||||
|
[
|
||||||
|
{ text: __('No') },
|
||||||
|
{
|
||||||
|
text: __('Yes'),
|
||||||
|
onPress: () => {
|
||||||
|
const resetAction = StackActions.reset({
|
||||||
|
index: 0,
|
||||||
|
actions: [
|
||||||
|
NavigationActions.navigate({ routeName: 'Splash', params: { resetUrl: 'lbry://?subscriptions' } }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
navigation.dispatch(resetAction);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
navigation.openDrawer();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
@ -242,7 +290,7 @@ class UriBar extends React.PureComponent {
|
||||||
size={24}
|
size={24}
|
||||||
style={uriBarStyle.drawerMenuButton}
|
style={uriBarStyle.drawerMenuButton}
|
||||||
iconStyle={uriBarStyle.drawerHamburger}
|
iconStyle={uriBarStyle.drawerHamburger}
|
||||||
onPress={() => navigation.openDrawer()}
|
onPress={this.handleNavigationButtonPress}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!selectionMode && (
|
{!selectionMode && (
|
||||||
|
@ -250,12 +298,10 @@ class UriBar extends React.PureComponent {
|
||||||
ref={ref => {
|
ref={ref => {
|
||||||
this.textInput = ref;
|
this.textInput = ref;
|
||||||
}}
|
}}
|
||||||
|
onTouchStart={this.handleTouchStart}
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
style={uriBarStyle.uriText}
|
style={uriBarStyle.uriText}
|
||||||
onLayout={() => {
|
selection={this.state.selection}
|
||||||
this.setSelection();
|
|
||||||
}}
|
|
||||||
selectTextOnFocus
|
|
||||||
placeholder={__('Search movies, music, and more')}
|
placeholder={__('Search movies, music, and more')}
|
||||||
underlineColorAndroid={'transparent'}
|
underlineColorAndroid={'transparent'}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
|
@ -264,11 +310,8 @@ class UriBar extends React.PureComponent {
|
||||||
returnKeyType={'go'}
|
returnKeyType={'go'}
|
||||||
inlineImageLeft={'baseline_search_black_24'}
|
inlineImageLeft={'baseline_search_black_24'}
|
||||||
inlineImagePadding={16}
|
inlineImagePadding={16}
|
||||||
onFocus={() => this.setState({ focused: true })}
|
onFocus={this.handleFocus}
|
||||||
onBlur={() => {
|
onBlur={this.handleBlur}
|
||||||
this.setState({ focused: false });
|
|
||||||
this.setSelection();
|
|
||||||
}}
|
|
||||||
onChangeText={this.handleChangeText}
|
onChangeText={this.handleChangeText}
|
||||||
onSubmitEditing={this.handleSubmitEditing}
|
onSubmitEditing={this.handleSubmitEditing}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,7 +6,4 @@ const select = state => ({
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, null)(WalletBalance);
|
||||||
select,
|
|
||||||
null
|
|
||||||
)(WalletBalance);
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Image, Text, View } from 'react-native';
|
import { Image, Text, View } from 'react-native';
|
||||||
import { Lbry, formatCredits } from 'lbry-redux';
|
import { formatCredits } from 'lbry-redux';
|
||||||
import Address from 'component/address';
|
import { Lbryio } from 'lbryinc';
|
||||||
import Button from 'component/button';
|
import { formatUsd } from 'utils/helper';
|
||||||
import walletStyle from 'styles/wallet';
|
import walletStyle from 'styles/wallet';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -11,6 +11,18 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class WalletBalance extends React.PureComponent<Props> {
|
class WalletBalance extends React.PureComponent<Props> {
|
||||||
|
state = {
|
||||||
|
usdExchangeRate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
Lbryio.getExchangeRates().then(rates => {
|
||||||
|
if (!isNaN(rates.LBC_USD)) {
|
||||||
|
this.setState({ usdExchangeRate: rates.LBC_USD });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { balance } = this.props;
|
const { balance } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -21,6 +33,13 @@ class WalletBalance extends React.PureComponent<Props> {
|
||||||
<Text style={walletStyle.balance}>
|
<Text style={walletStyle.balance}>
|
||||||
{(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'}
|
{(balance || balance === 0) && formatCredits(parseFloat(balance), 2) + ' LBC'}
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text style={walletStyle.usdBalance}>
|
||||||
|
{this.state.usdExchangeRate > 0 && (
|
||||||
|
<Text>
|
||||||
|
≈{formatUsd(isNaN(balance) ? 0 : parseFloat(balance) * parseFloat(this.state.usdExchangeRate))}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
14
src/component/walletBalanceExtra/index.js
Normal file
14
src/component/walletBalanceExtra/index.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectClaimsBalance, selectSupportsBalance, selectTipsBalance } from 'lbry-redux';
|
||||||
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import WalletBalanceExtra from './view';
|
||||||
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
claimsBalance: selectClaimsBalance(state) || 0,
|
||||||
|
deviceWalletSynced: makeSelectClientSetting(Constants.SETTING_DEVICE_WALLET_SYNCED)(state),
|
||||||
|
supportsBalance: selectSupportsBalance(state) || 0,
|
||||||
|
tipsBalance: selectTipsBalance(state) || 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, null)(WalletBalanceExtra);
|
115
src/component/walletBalanceExtra/view.js
Normal file
115
src/component/walletBalanceExtra/view.js
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import { formatCredits } from 'lbry-redux';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import { formatUsd } from 'utils/helper';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import Link from 'component/link';
|
||||||
|
import walletStyle from 'styles/wallet';
|
||||||
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
claimsBalance: number,
|
||||||
|
supportsBalance: number,
|
||||||
|
tipsBalance: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
class WalletBalanceExtra extends React.PureComponent<Props> {
|
||||||
|
state = {
|
||||||
|
usdExchangeRate: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
Lbryio.getExchangeRates().then(rates => {
|
||||||
|
if (!isNaN(rates.LBC_USD)) {
|
||||||
|
this.setState({ usdExchangeRate: rates.LBC_USD });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { claimsBalance, deviceWalletSynced, navigation, supportsBalance, tipsBalance } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={walletStyle.balanceExtra}>
|
||||||
|
<View style={walletStyle.usdInfoCard}>
|
||||||
|
<Text style={walletStyle.usdInfoText}>
|
||||||
|
You can convert your credits to USD and withdraw the converted amount using an exchange.{' '}
|
||||||
|
<Link
|
||||||
|
style={walletStyle.usdConvertFaqLink}
|
||||||
|
href={'https://lbry.com/faq/exchanges'}
|
||||||
|
text={__('Learn more')}
|
||||||
|
/>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
<Link
|
||||||
|
style={walletStyle.usdConvertLink}
|
||||||
|
href={'https://bittrex.com/Account/Register?referralCode=4M1-P30-BON'}
|
||||||
|
text={__('Convert credits to USD on Bittrex')}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={walletStyle.balanceExtraCard}>
|
||||||
|
<View style={walletStyle.walletExtraRow}>
|
||||||
|
<View style={walletStyle.walletExtraCol}>
|
||||||
|
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'gift'} size={16} />
|
||||||
|
<Text style={walletStyle.walletExtraCaption}>{__('You also have')}</Text>
|
||||||
|
<View style={walletStyle.balanceRow}>
|
||||||
|
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(tipsBalance), 2)}</Text>
|
||||||
|
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={walletStyle.usdWalletExtraBalance}>
|
||||||
|
≈{formatUsd(parseFloat(tipsBalance) * parseFloat(this.state.usdExchangeRate))}
|
||||||
|
</Text>
|
||||||
|
<Text style={walletStyle.text}>{__('in tips')}</Text>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
style={walletStyle.earnTipsLink}
|
||||||
|
onPress={() => {
|
||||||
|
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH });
|
||||||
|
}}
|
||||||
|
text={__('Earn more tips by uploading cool videos')}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={walletStyle.walletExtraCol}>
|
||||||
|
<Icon style={walletStyle.walletExtraIcon} color={Colors.LbryGreen} name={'lock'} size={16} />
|
||||||
|
<Text style={walletStyle.walletExtraCaption}>{__('You staked')}</Text>
|
||||||
|
<View style={walletStyle.balanceRow}>
|
||||||
|
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(claimsBalance), 2)}</Text>
|
||||||
|
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={walletStyle.text}>{__('in your publishes')}</Text>
|
||||||
|
<View style={[walletStyle.balanceRow, walletStyle.walletExtraTopMargin]}>
|
||||||
|
<Text style={walletStyle.walletExtraBalance}>{formatCredits(parseFloat(supportsBalance), 2)}</Text>
|
||||||
|
<Text style={walletStyle.walletExtraCurrency}>LBC</Text>
|
||||||
|
</View>
|
||||||
|
<Text style={walletStyle.text}>{__('in your supports')}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={walletStyle.syncDriverCustody}>
|
||||||
|
<Text style={walletStyle.syncInfoText}>
|
||||||
|
{deviceWalletSynced
|
||||||
|
? __('A backup of your wallet is synced with lbry.tv')
|
||||||
|
: __('Your wallet is not currently synced with lbry.tv. You are responsible for backing up your wallet.')}
|
||||||
|
</Text>
|
||||||
|
<Link
|
||||||
|
text={__('What does this mean?')}
|
||||||
|
href={
|
||||||
|
deviceWalletSynced
|
||||||
|
? 'https://lbry.com/faq/account-sync'
|
||||||
|
: 'https://lbry.com/faq/how-to-backup-wallet#android'
|
||||||
|
}
|
||||||
|
style={walletStyle.syncInfoLink}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WalletBalanceExtra;
|
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { regexAddress } from 'lbry-redux';
|
import { formatCredits, regexAddress } from 'lbry-redux';
|
||||||
import { Alert, Clipboard, TextInput, Text, View } from 'react-native';
|
import { Alert, Clipboard, TextInput, Text, View } from 'react-native';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import walletStyle from 'styles/wallet';
|
import walletStyle from 'styles/wallet';
|
||||||
|
|
||||||
type DraftTransaction = {
|
type DraftTransaction = {
|
||||||
|
@ -24,6 +25,7 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
address: null,
|
address: null,
|
||||||
addressChanged: false,
|
addressChanged: false,
|
||||||
addressValid: false,
|
addressValid: false,
|
||||||
|
creditsInputFocused: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
|
@ -36,9 +38,10 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
handleSend = () => {
|
handleSend = () => {
|
||||||
const { balance, sendToAddress, notify } = this.props;
|
const { balance, sendToAddress, notify } = this.props;
|
||||||
const { address, amount } = this.state;
|
const { address, amount } = this.state;
|
||||||
if (address && !regexAddress.test(address)) {
|
if (address && (!regexAddress.test(address) || address.substring(0, 1) === 'r')) {
|
||||||
notify({
|
notify({
|
||||||
message: __('The recipient address is not a valid LBRY address.'),
|
message: __('The recipient address is not a valid LBRY address.'),
|
||||||
|
isError: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +49,7 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
if (amount > balance) {
|
if (amount > balance) {
|
||||||
notify({
|
notify({
|
||||||
message: __('Insufficient credits'),
|
message: __('Insufficient credits'),
|
||||||
|
isError: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -70,6 +74,7 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
const { notify } = this.props;
|
const { notify } = this.props;
|
||||||
notify({
|
notify({
|
||||||
message: __('The recipient address is not a valid LBRY address.'),
|
message: __('The recipient address is not a valid LBRY address.'),
|
||||||
|
isError: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -95,9 +100,10 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
this.setState({
|
this.setState({
|
||||||
address: value,
|
address: value,
|
||||||
addressChanged: true,
|
addressChanged: true,
|
||||||
addressValid: value.trim().length === 0 || regexAddress.test(value),
|
addressValid: value.trim().length === 0 || (regexAddress.test(value) && !value.startsWith('r')),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
numberOfLines={1}
|
||||||
onBlur={this.handleAddressInputBlur}
|
onBlur={this.handleAddressInputBlur}
|
||||||
onSubmitEditing={this.handleAddressInputSubmit}
|
onSubmitEditing={this.handleAddressInputSubmit}
|
||||||
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
|
placeholder={'bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs'}
|
||||||
|
@ -118,6 +124,8 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={ref => (this.amountInput = ref)}
|
ref={ref => (this.amountInput = ref)}
|
||||||
onChangeText={value => this.setState({ amount: value })}
|
onChangeText={value => this.setState({ amount: value })}
|
||||||
|
onFocus={() => this.setState({ creditsInputFocused: true })}
|
||||||
|
onBlur={() => this.setState({ creditsInputFocused: false })}
|
||||||
keyboardType={'numeric'}
|
keyboardType={'numeric'}
|
||||||
placeholder={'0'}
|
placeholder={'0'}
|
||||||
underlineColorAndroid={Colors.NextLbryGreen}
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
@ -125,6 +133,12 @@ class WalletSend extends React.PureComponent<Props> {
|
||||||
style={[walletStyle.input, walletStyle.amountInput]}
|
style={[walletStyle.input, walletStyle.amountInput]}
|
||||||
/>
|
/>
|
||||||
<Text style={[walletStyle.text, walletStyle.currency]}>LBC</Text>
|
<Text style={[walletStyle.text, walletStyle.currency]}>LBC</Text>
|
||||||
|
<View style={walletStyle.balanceFocus}>
|
||||||
|
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
|
||||||
|
{this.state.creditsInputFocused && (
|
||||||
|
<Text style={walletStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
text={__('Send')}
|
text={__('Send')}
|
||||||
|
|
|
@ -27,7 +27,7 @@ class WalletSyncDriver extends React.PureComponent<Props> {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: true }
|
{ cancelable: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,6 +44,10 @@ const Constants = {
|
||||||
SETTING_BACKUP_DISMISSED: 'backupDismissed',
|
SETTING_BACKUP_DISMISSED: 'backupDismissed',
|
||||||
SETTING_REWARDS_NOT_INTERESTED: 'rewardsNotInterested',
|
SETTING_REWARDS_NOT_INTERESTED: 'rewardsNotInterested',
|
||||||
SETTING_DEVICE_WALLET_SYNCED: 'deviceWalletSynced',
|
SETTING_DEVICE_WALLET_SYNCED: 'deviceWalletSynced',
|
||||||
|
SETTING_DHT_ENABLED: 'dhtEnabled',
|
||||||
|
SETTING_NEW_ANDROID_REWARD_CLAIMED: 'newAndroidRewardClaimed',
|
||||||
|
|
||||||
|
ACTION_SDK_READY: 'SDK_READY',
|
||||||
|
|
||||||
ACTION_DELETE_COMPLETED_BLOBS: 'DELETE_COMPLETED_BLOBS',
|
ACTION_DELETE_COMPLETED_BLOBS: 'DELETE_COMPLETED_BLOBS',
|
||||||
ACTION_FIRST_RUN_PAGE_CHANGED: 'FIRST_RUN_PAGE_CHANGED',
|
ACTION_FIRST_RUN_PAGE_CHANGED: 'FIRST_RUN_PAGE_CHANGED',
|
||||||
|
@ -64,6 +68,8 @@ const Constants = {
|
||||||
ACTION_CLEAR_PUBLISH_FORM_STATE: 'CLEAR_PUBLISH_FORM_STATE',
|
ACTION_CLEAR_PUBLISH_FORM_STATE: 'CLEAR_PUBLISH_FORM_STATE',
|
||||||
ACTION_CLEAR_CHANNEL_FORM_STATE: 'CLEAR_CHANNEL_FORM_STATE',
|
ACTION_CLEAR_CHANNEL_FORM_STATE: 'CLEAR_CHANNEL_FORM_STATE',
|
||||||
|
|
||||||
|
ACTION_SET_EXPLICIT_NAVIGATE_BACK: 'SET_EXPLICIT_NAVIGATE_BACK',
|
||||||
|
|
||||||
ACTION_FULLSCREEN_MODE_TOGGLED: 'FULLSCREEN_MODE_TOGGLED',
|
ACTION_FULLSCREEN_MODE_TOGGLED: 'FULLSCREEN_MODE_TOGGLED',
|
||||||
|
|
||||||
ORIENTATION_HORIZONTAL: 'horizontal',
|
ORIENTATION_HORIZONTAL: 'horizontal',
|
||||||
|
@ -90,6 +96,8 @@ const Constants = {
|
||||||
DRAWER_ROUTE_TAG: 'Tag',
|
DRAWER_ROUTE_TAG: 'Tag',
|
||||||
DRAWER_ROUTE_CHANNEL_CREATOR: 'ChannelCreator',
|
DRAWER_ROUTE_CHANNEL_CREATOR: 'ChannelCreator',
|
||||||
DRAWER_ROUTE_CHANNEL_CREATOR_FORM: 'ChannnelCreatorForm',
|
DRAWER_ROUTE_CHANNEL_CREATOR_FORM: 'ChannnelCreatorForm',
|
||||||
|
DRAWER_ROUTE_INVITES: 'Invites',
|
||||||
|
DRAWER_ROUTE_LITE_FILE: 'LiteFile',
|
||||||
|
|
||||||
FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack',
|
FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack',
|
||||||
FULL_ROUTE_NAME_WALLET: 'WalletStack',
|
FULL_ROUTE_NAME_WALLET: 'WalletStack',
|
||||||
|
@ -134,7 +142,7 @@ const Constants = {
|
||||||
|
|
||||||
ORDER_BY_EFFECTIVE_AMOUNT: 'effective_amount',
|
ORDER_BY_EFFECTIVE_AMOUNT: 'effective_amount',
|
||||||
|
|
||||||
DEFAULT_PAGE_SIZE: 10,
|
DEFAULT_PAGE_SIZE: 20,
|
||||||
|
|
||||||
ALL_PLACEHOLDER: '_all',
|
ALL_PLACEHOLDER: '_all',
|
||||||
|
|
||||||
|
@ -142,7 +150,7 @@ const Constants = {
|
||||||
|
|
||||||
TRUE_STRING: 'true',
|
TRUE_STRING: 'true',
|
||||||
|
|
||||||
MINIMUM_TRANSACTION_BALANCE: 0.1,
|
MINIMUM_TRANSACTION_BALANCE: 0.01,
|
||||||
|
|
||||||
SHARE_BASE_URL: 'https://open.lbry.com',
|
SHARE_BASE_URL: 'https://open.lbry.com',
|
||||||
};
|
};
|
||||||
|
@ -165,6 +173,7 @@ export const DrawerRoutes = [
|
||||||
Constants.DRAWER_ROUTE_SEARCH,
|
Constants.DRAWER_ROUTE_SEARCH,
|
||||||
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY,
|
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY,
|
||||||
Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
|
Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
|
||||||
|
Constants.DRAWER_ROUTE_INVITES,
|
||||||
];
|
];
|
||||||
|
|
||||||
// sub-pages for main routes
|
// sub-pages for main routes
|
||||||
|
|
|
@ -52,8 +52,9 @@ function checkMessageAndSave(message, messagesFilePath) {
|
||||||
); */
|
); */
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err) {
|
if (err && !isProduction) {
|
||||||
throw err;
|
// only do this when not in production
|
||||||
|
console.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -74,6 +75,6 @@ export function __(message, tokens) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return translatedMessage.replace(/%([^%]+)%/g, function($1, $2) {
|
return translatedMessage.replace(/%([^%]+)%/g, function($1, $2) {
|
||||||
return tokens[$2] || $2;
|
return tokens.hasOwnProperty($2) ? tokens[$2] : $2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
22
src/index.js
22
src/index.js
|
@ -27,6 +27,7 @@ import {
|
||||||
homepageReducer,
|
homepageReducer,
|
||||||
rewardsReducer,
|
rewardsReducer,
|
||||||
selectUserVerifiedEmail,
|
selectUserVerifiedEmail,
|
||||||
|
statsReducer,
|
||||||
subscriptionsReducer,
|
subscriptionsReducer,
|
||||||
syncReducer,
|
syncReducer,
|
||||||
userReducer,
|
userReducer,
|
||||||
|
@ -41,6 +42,7 @@ import AppWithNavigationState, {
|
||||||
} from 'component/AppNavigator';
|
} from 'component/AppNavigator';
|
||||||
import { REHYDRATE, PURGE, persistCombineReducers, persistStore } from 'redux-persist';
|
import { REHYDRATE, PURGE, persistCombineReducers, persistStore } from 'redux-persist';
|
||||||
import { __ } from 'i18n';
|
import { __ } from 'i18n';
|
||||||
|
import reactotron from '../reactotron';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4';
|
import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4';
|
||||||
import FilesystemStorage from 'redux-persist-filesystem-storage';
|
import FilesystemStorage from 'redux-persist-filesystem-storage';
|
||||||
|
@ -53,11 +55,14 @@ import settingsReducer from 'redux/reducers/settings';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
|
||||||
window.__ = __;
|
window.__ = __;
|
||||||
|
if (!NativeModules.UtilityModule.dhtEnabled) {
|
||||||
|
Lbry.alternateConnectionString = 'https://api.lbry.tv/api/v1/proxy';
|
||||||
|
Lbry.methodsUsingAlternateConnectionString = ['claim_search', 'resolve'];
|
||||||
|
}
|
||||||
|
|
||||||
const globalExceptionHandler = (error, isFatal) => {
|
const globalExceptionHandler = (error, isFatal) => {
|
||||||
if (error && NativeModules.Firebase) {
|
if (error && NativeModules.Firebase) {
|
||||||
console.log(error);
|
NativeModules.Firebase.logException(!!isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
|
||||||
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
setJSExceptionHandler(globalExceptionHandler, true);
|
setJSExceptionHandler(globalExceptionHandler, true);
|
||||||
|
@ -94,6 +99,7 @@ const compressor = createCompressor();
|
||||||
const authFilter = createFilter('auth', ['authToken']);
|
const authFilter = createFilter('auth', ['authToken']);
|
||||||
const blockedFilter = createFilter('blocked', ['blockedChannels']);
|
const blockedFilter = createFilter('blocked', ['blockedChannels']);
|
||||||
const contentFilter = createFilter('content', ['positions']);
|
const contentFilter = createFilter('content', ['positions']);
|
||||||
|
const drawerFilter = createFilter('drawer', ['lastRouteInStack']);
|
||||||
const saveClaimsFilter = createFilter('claims', ['claimsByUri']);
|
const saveClaimsFilter = createFilter('claims', ['claimsByUri']);
|
||||||
const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions', 'latest']);
|
const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions', 'latest']);
|
||||||
const settingsFilter = createFilter('settings', ['clientSettings']);
|
const settingsFilter = createFilter('settings', ['clientSettings']);
|
||||||
|
@ -101,12 +107,13 @@ const tagsFilter = createFilter('tags', ['followedTags']);
|
||||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||||
|
|
||||||
const v4PersistOptions = {
|
const v4PersistOptions = {
|
||||||
whitelist: ['auth', 'blocked', 'claims', 'content', 'subscriptions', 'settings', 'tags', 'wallet'],
|
whitelist: ['auth', 'blocked', 'claims', 'drawer', 'content', 'subscriptions', 'settings', 'tags', 'wallet'],
|
||||||
// Order is important. Needs to be compressed last or other transforms can't
|
// Order is important. Needs to be compressed last or other transforms can't
|
||||||
// read the data
|
// read the data
|
||||||
transforms: [
|
transforms: [
|
||||||
authFilter,
|
authFilter,
|
||||||
blockedFilter,
|
blockedFilter,
|
||||||
|
drawerFilter,
|
||||||
saveClaimsFilter,
|
saveClaimsFilter,
|
||||||
subscriptionsFilter,
|
subscriptionsFilter,
|
||||||
settingsFilter,
|
settingsFilter,
|
||||||
|
@ -141,6 +148,7 @@ const reducers = persistCombineReducers(persistOptions, {
|
||||||
rewards: rewardsReducer,
|
rewards: rewardsReducer,
|
||||||
settings: settingsReducer,
|
settings: settingsReducer,
|
||||||
search: searchReducer,
|
search: searchReducer,
|
||||||
|
stats: statsReducer,
|
||||||
subscriptions: subscriptionsReducer,
|
subscriptions: subscriptionsReducer,
|
||||||
sync: syncReducer,
|
sync: syncReducer,
|
||||||
tags: tagsReducer,
|
tags: tagsReducer,
|
||||||
|
@ -178,7 +186,7 @@ const sharedStateCallback = ({ dispatch, getState }) => {
|
||||||
const emailVerified = selectUserVerifiedEmail(state);
|
const emailVerified = selectUserVerifiedEmail(state);
|
||||||
if (syncEnabled && emailVerified) {
|
if (syncEnabled && emailVerified) {
|
||||||
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password =>
|
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password =>
|
||||||
dispatch(doGetSync(password))
|
dispatch(doGetSync(password)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -194,13 +202,13 @@ const composeEnhancers = compose;
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
enableBatching(reducers),
|
enableBatching(reducers),
|
||||||
{}, // initial state,
|
{}, // initial state,
|
||||||
composeEnhancers(applyMiddleware(...middleware))
|
composeEnhancers(applyMiddleware(...middleware), reactotron.createEnhancer()),
|
||||||
);
|
);
|
||||||
window.store = store;
|
window.store = store;
|
||||||
|
|
||||||
const persistor = persistStore(store, persistOptions, err => {
|
const persistor = persistStore(store, null, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log('Unable to load saved SETTINGS');
|
// console.log('Unable to load saved SETTINGS');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.persistor = persistor;
|
window.persistor = persistor;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import aboutStyle from 'styles/about';
|
||||||
class AboutPage extends React.PureComponent {
|
class AboutPage extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
appVersion: null,
|
appVersion: null,
|
||||||
|
firebaseToken: null,
|
||||||
lbryId: null,
|
lbryId: null,
|
||||||
versionInfo: null,
|
versionInfo: null,
|
||||||
};
|
};
|
||||||
|
@ -45,11 +46,12 @@ class AboutPage extends React.PureComponent {
|
||||||
setPlayerVisible();
|
setPlayerVisible();
|
||||||
|
|
||||||
NativeModules.Firebase.setCurrentScreen('About').then(result => {
|
NativeModules.Firebase.setCurrentScreen('About').then(result => {
|
||||||
if (NativeModules.VersionInfo) {
|
NativeModules.VersionInfo.getAppVersion().then(version => {
|
||||||
NativeModules.VersionInfo.getAppVersion().then(version => {
|
this.setState({ appVersion: version });
|
||||||
this.setState({ appVersion: version });
|
});
|
||||||
});
|
NativeModules.Firebase.getMessagingToken().then(firebaseToken => {
|
||||||
}
|
this.setState({ firebaseToken });
|
||||||
|
});
|
||||||
Lbry.version().then(info => {
|
Lbry.version().then(info => {
|
||||||
this.setState({
|
this.setState({
|
||||||
versionInfo: info,
|
versionInfo: info,
|
||||||
|
@ -77,7 +79,7 @@ class AboutPage extends React.PureComponent {
|
||||||
<Text style={aboutStyle.title}>{__('Content Freedom')}</Text>
|
<Text style={aboutStyle.title}>{__('Content Freedom')}</Text>
|
||||||
<Text style={aboutStyle.paragraph}>
|
<Text style={aboutStyle.paragraph}>
|
||||||
{__(
|
{__(
|
||||||
'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.',
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={aboutStyle.links}>
|
<View style={aboutStyle.links}>
|
||||||
|
@ -88,7 +90,7 @@ class AboutPage extends React.PureComponent {
|
||||||
<Text style={aboutStyle.socialTitle}>{__('Get Social')}</Text>
|
<Text style={aboutStyle.socialTitle}>{__('Get Social')}</Text>
|
||||||
<Text style={aboutStyle.paragraph}>
|
<Text style={aboutStyle.paragraph}>
|
||||||
{__(
|
{__(
|
||||||
'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.',
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
<View style={aboutStyle.links}>
|
<View style={aboutStyle.links}>
|
||||||
|
@ -164,6 +166,15 @@ class AboutPage extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={aboutStyle.row}>
|
||||||
|
<View style={aboutStyle.col}>
|
||||||
|
<Text style={aboutStyle.text}>{__('Firebase Token')}</Text>
|
||||||
|
<Text selectable style={aboutStyle.lineValueText}>
|
||||||
|
{this.state.firebaseToken ? this.state.firebaseToken : loading}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<View style={aboutStyle.row}>
|
<View style={aboutStyle.row}>
|
||||||
<View style={aboutStyle.col}>
|
<View style={aboutStyle.col}>
|
||||||
<Text style={aboutStyle.text}>{__('Logs')}</Text>
|
<Text style={aboutStyle.text}>{__('Logs')}</Text>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doAbandonClaim, doFetchChannelListMine, makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux';
|
import { doAbandonClaim, doFetchChannelListMine, makeSelectClaimForUri, selectMyChannelClaims } from 'lbry-redux';
|
||||||
|
import { doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
|
||||||
import { doPopDrawerStack } from 'redux/actions/drawer';
|
import { doPopDrawerStack } from 'redux/actions/drawer';
|
||||||
import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
|
import { doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
|
||||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||||
|
@ -11,12 +12,14 @@ const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
drawerStack: selectDrawerStack(state),
|
drawerStack: selectDrawerStack(state),
|
||||||
sortByItem: selectSortByItem(state),
|
sortByItem: selectSortByItem(state),
|
||||||
|
subCount: makeSelectSubCountForUri(props.uri)(state),
|
||||||
timeItem: selectTimeItem(state),
|
timeItem: selectTimeItem(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
|
||||||
|
fetchSubCount: claimId => dispatch(doFetchSubCount(claimId)),
|
||||||
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
||||||
setSortByItem: item => dispatch(doSetSortByItem(item)),
|
setSortByItem: item => dispatch(doSetSortByItem(item)),
|
||||||
setTimeItem: item => dispatch(doSetTimeItem(item)),
|
setTimeItem: item => dispatch(doSetTimeItem(item)),
|
||||||
|
@ -24,5 +27,5 @@ const perform = dispatch => ({
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
select,
|
select,
|
||||||
perform
|
perform,
|
||||||
)(ChannelPage);
|
)(ChannelPage);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import EmptyStateView from 'component/emptyStateView';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
import ModalPicker from 'component/modalPicker';
|
import ModalPicker from 'component/modalPicker';
|
||||||
|
import ModalTipView from 'component/modalTipView';
|
||||||
import PageHeader from 'component/pageHeader';
|
import PageHeader from 'component/pageHeader';
|
||||||
import SubscribeButton from 'component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import SubscribeNotificationButton from 'component/subscribeNotificationButton';
|
import SubscribeNotificationButton from 'component/subscribeNotificationButton';
|
||||||
|
@ -36,6 +37,7 @@ class ChannelPage extends React.PureComponent {
|
||||||
autoStyle: null,
|
autoStyle: null,
|
||||||
showSortPicker: false,
|
showSortPicker: false,
|
||||||
showTimePicker: false,
|
showTimePicker: false,
|
||||||
|
showTipView: false,
|
||||||
orderBy: ['release_time'], // sort by new by default
|
orderBy: ['release_time'], // sort by new by default
|
||||||
activeTab: Constants.CONTENT_TAB,
|
activeTab: Constants.CONTENT_TAB,
|
||||||
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting channel pages by new
|
currentSortByItem: Constants.CLAIM_SEARCH_SORT_BY_ITEMS[1], // should always default to sorting channel pages by new
|
||||||
|
@ -49,9 +51,12 @@ class ChannelPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { fetchChannelListMine } = this.props;
|
const { claim, fetchChannelListMine, fetchSubCount } = this.props;
|
||||||
NativeModules.Firebase.setCurrentScreen('Channel');
|
NativeModules.Firebase.setCurrentScreen('Channel');
|
||||||
fetchChannelListMine();
|
fetchChannelListMine();
|
||||||
|
if (claim) {
|
||||||
|
fetchSubCount(claim.claim_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSortByItemSelected = item => {
|
handleSortByItemSelected = item => {
|
||||||
|
@ -168,9 +173,14 @@ class ChannelPage extends React.PureComponent {
|
||||||
const { permanent_url: permanentUrl } = claim;
|
const { permanent_url: permanentUrl } = claim;
|
||||||
navigation.navigate({
|
navigation.navigate({
|
||||||
routeName: Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
|
routeName: Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
|
||||||
params: { editChannelUrl: permanentUrl },
|
params: { editChannelUrl: permanentUrl, returnUrl: permanentUrl },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.onEditPressed = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
onTipPressed = () => {
|
||||||
|
this.setState({ showTipView: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSharePressed = () => {
|
onSharePressed = () => {
|
||||||
|
@ -201,15 +211,15 @@ class ChannelPage extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: true }
|
{ cancelable: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { channels, claim, navigation, uri, drawerStack, popDrawerStack, timeItem } = this.props;
|
const { channels, claim, navigation, uri, drawerStack, popDrawerStack, subCount, timeItem } = this.props;
|
||||||
const { name, permanent_url: permanentUrl } = claim;
|
const { name, permanent_url: permanentUrl } = claim;
|
||||||
const { autoStyle, currentSortByItem, showSortPicker, showTimePicker } = this.state;
|
const { autoStyle, currentSortByItem, showSortPicker, showTimePicker, showTipView } = this.state;
|
||||||
const ownedChannel = channels ? channels.map(channel => channel.permanent_url).includes(permanentUrl) : false;
|
const ownedChannel = channels ? channels.map(channel => channel.permanent_url).includes(permanentUrl) : false;
|
||||||
|
|
||||||
let thumbnailUrl,
|
let thumbnailUrl,
|
||||||
|
@ -252,6 +262,10 @@ class ChannelPage extends React.PureComponent {
|
||||||
|
|
||||||
<View style={channelPageStyle.channelHeader}>
|
<View style={channelPageStyle.channelHeader}>
|
||||||
<Text style={channelPageStyle.channelName}>{title && title.trim().length > 0 ? title : name}</Text>
|
<Text style={channelPageStyle.channelName}>{title && title.trim().length > 0 ? title : name}</Text>
|
||||||
|
<Text style={[channelPageStyle.followerCount, subCount >= 1 ? channelPageStyle.followerCountBg : null]}>
|
||||||
|
{subCount === 1 && __('%follower% follower', { follower: subCount })}
|
||||||
|
{subCount > 1 && __('%follower% followers', { follower: subCount })}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={[channelPageStyle.avatarImageContainer, autoStyle]}>
|
<View style={[channelPageStyle.avatarImageContainer, autoStyle]}>
|
||||||
|
@ -290,6 +304,12 @@ class ChannelPage extends React.PureComponent {
|
||||||
icon={'share-alt'}
|
icon={'share-alt'}
|
||||||
onPress={this.onSharePressed}
|
onPress={this.onSharePressed}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
style={[channelPageStyle.actionButton, channelPageStyle.tipButton]}
|
||||||
|
theme={'light'}
|
||||||
|
icon={'gift'}
|
||||||
|
onPress={this.onTipPressed}
|
||||||
|
/>
|
||||||
{!ownedChannel && <SubscribeButton style={channelPageStyle.subscribeButton} uri={fullUri} name={name} />}
|
{!ownedChannel && <SubscribeButton style={channelPageStyle.subscribeButton} uri={fullUri} name={name} />}
|
||||||
{false && !ownedChannel && (
|
{false && !ownedChannel && (
|
||||||
<SubscribeNotificationButton
|
<SubscribeNotificationButton
|
||||||
|
@ -340,6 +360,16 @@ class ChannelPage extends React.PureComponent {
|
||||||
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
|
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{showTipView && (
|
||||||
|
<ModalTipView
|
||||||
|
claim={claim}
|
||||||
|
channelName={claim.name}
|
||||||
|
contentName={title}
|
||||||
|
onCancelPress={() => this.setState({ showTipView: false })}
|
||||||
|
onOverlayPress={() => this.setState({ showTipView: false })}
|
||||||
|
onSendTipSuccessful={() => this.setState({ showTipView: false })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,16 @@ import {
|
||||||
doToast,
|
doToast,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doGetSync } from 'lbryinc';
|
import { doGetSync } from 'lbryinc';
|
||||||
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
import {
|
||||||
|
doPushDrawerStack,
|
||||||
|
doPopDrawerStack,
|
||||||
|
doSetPlayerVisible,
|
||||||
|
doSetExplicitNavigateBack,
|
||||||
|
} from 'redux/actions/drawer';
|
||||||
import { doUpdateChannelFormState, doClearChannelFormState } from 'redux/actions/form';
|
import { doUpdateChannelFormState, doClearChannelFormState } from 'redux/actions/form';
|
||||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||||
import { selectChannelFormState, selectHasChannelFormState } from 'redux/selectors/form';
|
import { selectChannelFormState, selectHasChannelFormState } from 'redux/selectors/form';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import ChannelCreator from './view';
|
import ChannelCreator from './view';
|
||||||
|
|
||||||
|
@ -28,6 +34,7 @@ const select = state => ({
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
hasFormState: selectHasChannelFormState(state),
|
hasFormState: selectHasChannelFormState(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
updatingChannel: selectUpdatingChannel(state),
|
updatingChannel: selectUpdatingChannel(state),
|
||||||
updateChannelError: selectUpdateChannelError(state),
|
updateChannelError: selectUpdateChannelError(state),
|
||||||
});
|
});
|
||||||
|
@ -37,16 +44,14 @@ const perform = dispatch => ({
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
clearChannelFormState: () => dispatch(doClearChannelFormState()),
|
clearChannelFormState: () => dispatch(doClearChannelFormState()),
|
||||||
createChannel: (name, amount, optionalParams) => dispatch(doCreateChannel(name, amount, optionalParams)),
|
createChannel: (name, amount, optionalParams) => dispatch(doCreateChannel(name, amount, optionalParams)),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
|
||||||
getSync: (password, callback) => dispatch(doGetSync(password, callback)),
|
getSync: (password, callback) => dispatch(doGetSync(password, callback)),
|
||||||
updateChannel: params => dispatch(doUpdateChannel(params)),
|
updateChannel: params => dispatch(doUpdateChannel(params)),
|
||||||
updateChannelFormState: data => dispatch(doUpdateChannelFormState(data)),
|
updateChannelFormState: data => dispatch(doUpdateChannelFormState(data)),
|
||||||
pushDrawerStack: (routeName, params) => dispatch(doPushDrawerStack(routeName, params)),
|
pushDrawerStack: (routeName, params) => dispatch(doPushDrawerStack(routeName, params)),
|
||||||
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
||||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
|
setExplicitNavigateBack: flag => dispatch(doSetExplicitNavigateBack(flag)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(ChannelCreator);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(ChannelCreator);
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CLAIM_VALUES, isNameValid, regexInvalidURI } from 'lbry-redux';
|
import { CLAIM_VALUES, Lbry, formatCredits, isNameValid, regexInvalidURI } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
Alert,
|
Alert,
|
||||||
|
@ -14,13 +14,14 @@ import {
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
View,
|
View,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { navigateToUri, logPublish, uploadImageAsset } from 'utils/helper';
|
import { navigateBack, navigateToUri, logPublish, uploadImageAsset } from 'utils/helper';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ChannelIconItem from 'component/channelIconItem';
|
import ChannelIconItem from 'component/channelIconItem';
|
||||||
import ChannelRewardsDriver from 'component/channelRewardsDriver';
|
import ChannelRewardsDriver from 'component/channelRewardsDriver';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import EmptyStateView from 'component/emptyStateView';
|
import EmptyStateView from 'component/emptyStateView';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
|
@ -36,15 +37,17 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
autoStyle: null,
|
autoStyle: null,
|
||||||
|
returnUrl: null,
|
||||||
canSave: true,
|
canSave: true,
|
||||||
claimId: null,
|
claimId: null,
|
||||||
|
creditsInputFocused: false,
|
||||||
currentSelectedValue: Constants.ITEM_ANONYMOUS,
|
currentSelectedValue: Constants.ITEM_ANONYMOUS,
|
||||||
currentPhase: null,
|
currentPhase: null,
|
||||||
displayName: null,
|
displayName: null,
|
||||||
channelNameUserEdited: false,
|
channelNameUserEdited: false,
|
||||||
newChannelTitle: '',
|
newChannelTitle: '',
|
||||||
newChannelName: '',
|
newChannelName: '',
|
||||||
newChannelBid: 0.1,
|
newChannelBid: 0.01,
|
||||||
addingChannel: false,
|
addingChannel: false,
|
||||||
creatingChannel: false,
|
creatingChannel: false,
|
||||||
editChannelUrl: null,
|
editChannelUrl: null,
|
||||||
|
@ -75,6 +78,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
descriptionFocused: false,
|
descriptionFocused: false,
|
||||||
websiteFocused: false,
|
websiteFocused: false,
|
||||||
emailFocused: false,
|
emailFocused: false,
|
||||||
|
hasReturnedBack: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
didFocusListener;
|
didFocusListener;
|
||||||
|
@ -116,7 +120,14 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { currentRoute: prevRoute, drawerStack: prevDrawerStack, notify } = this.props;
|
const {
|
||||||
|
currentRoute: prevRoute,
|
||||||
|
drawerStack: prevDrawerStack,
|
||||||
|
popDrawerStack,
|
||||||
|
setPlayerVisible,
|
||||||
|
navigation,
|
||||||
|
notify,
|
||||||
|
} = this.props;
|
||||||
const { currentRoute, drawerStack, updatingChannel, updateChannelError } = nextProps;
|
const { currentRoute, drawerStack, updatingChannel, updateChannelError } = nextProps;
|
||||||
|
|
||||||
if (Constants.DRAWER_ROUTE_CHANNEL_CREATOR === currentRoute && currentRoute !== prevRoute) {
|
if (Constants.DRAWER_ROUTE_CHANNEL_CREATOR === currentRoute && currentRoute !== prevRoute) {
|
||||||
|
@ -140,6 +151,11 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
) {
|
) {
|
||||||
// navigated back from the form
|
// navigated back from the form
|
||||||
this.setState({ currentPhase: Constants.PHASE_LIST });
|
this.setState({ currentPhase: Constants.PHASE_LIST });
|
||||||
|
if (!this.state.hasReturnedBack && this.state.returnUrl) {
|
||||||
|
this.setState({ hasReturnedBack: true }, () => {
|
||||||
|
navigateBack(navigation, drawerStack, popDrawerStack, setPlayerVisible);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,20 +176,19 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
NativeModules.Firebase.setCurrentScreen('Channels').then(result => {
|
NativeModules.Firebase.setCurrentScreen('Channels').then(result => {
|
||||||
pushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR, navigation.state.params ? navigation.state.params : null);
|
pushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR, navigation.state.params ? navigation.state.params : null);
|
||||||
setPlayerVisible();
|
setPlayerVisible();
|
||||||
if (!fetchingChannels) {
|
fetchChannelListMine();
|
||||||
fetchChannelListMine();
|
|
||||||
}
|
|
||||||
|
|
||||||
DeviceEventEmitter.addListener('onDocumentPickerFilePicked', this.onFilePicked);
|
DeviceEventEmitter.addListener('onDocumentPickerFilePicked', this.onFilePicked);
|
||||||
DeviceEventEmitter.addListener('onDocumentPickerCanceled', this.onPickerCanceled);
|
DeviceEventEmitter.addListener('onDocumentPickerCanceled', this.onPickerCanceled);
|
||||||
|
|
||||||
let isEditMode = false;
|
let isEditMode = false;
|
||||||
if (navigation.state.params) {
|
if (navigation.state.params) {
|
||||||
const { editChannelUrl, displayForm } = navigation.state.params;
|
const { editChannelUrl, displayForm, returnUrl } = navigation.state.params;
|
||||||
if (editChannelUrl) {
|
if (editChannelUrl) {
|
||||||
isEditMode = true;
|
isEditMode = true;
|
||||||
this.setState({ editChannelUrl, currentPhase: Constants.PHASE_CREATE });
|
this.setState({ editChannelUrl, currentPhase: Constants.PHASE_CREATE });
|
||||||
}
|
}
|
||||||
|
this.setState({ returnUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEditMode && hasFormState) {
|
if (!isEditMode && hasFormState) {
|
||||||
|
@ -224,9 +239,9 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
error => {
|
error => {
|
||||||
notify({ message: `The image could not be uploaded: ${error}` });
|
notify({ message: `The image could not be uploaded: ${error}` });
|
||||||
this.setState({ uploadingImage: false });
|
this.setState({ uploadingImage: false });
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// could not determine the file path
|
// could not determine the file path
|
||||||
|
@ -265,7 +280,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
handleCreateCancel = () => {
|
handleCreateCancel = () => {
|
||||||
const { clearChannelFormState } = this.props;
|
const { clearChannelFormState } = this.props;
|
||||||
clearChannelFormState(); // explicitly clear state on cancel?
|
clearChannelFormState(); // explicitly clear state on cancel?
|
||||||
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.1 });
|
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.01 });
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePickerValueChange = (itemValue, itemIndex) => {
|
handlePickerValueChange = (itemValue, itemIndex) => {
|
||||||
|
@ -488,12 +503,12 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
claim_id: claimId,
|
claim_id: claimId,
|
||||||
amount: newChannelBid,
|
amount: newChannelBid,
|
||||||
},
|
},
|
||||||
optionalParams
|
optionalParams,
|
||||||
);
|
);
|
||||||
this.setState({ updateChannelStarted: true }, () => updateChannel(params));
|
this.setState({ updateChannelStarted: true }, () => updateChannel(params));
|
||||||
} else {
|
} else {
|
||||||
this.setState({ creatingChannel: true }, () =>
|
this.setState({ creatingChannel: true }, () =>
|
||||||
createChannel(channelName, newChannelBid, optionalParams).then(success, failure)
|
createChannel(channelName, newChannelBid, optionalParams).then(success, failure),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -526,7 +541,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
avatarImagePickerOpen: false,
|
avatarImagePickerOpen: false,
|
||||||
coverImagePickerOpen: true,
|
coverImagePickerOpen: true,
|
||||||
},
|
},
|
||||||
() => NativeModules.UtilityModule.openDocumentPicker('image/*')
|
() => this.checkStoragePermission(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -542,7 +557,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
avatarImagePickerOpen: true,
|
avatarImagePickerOpen: true,
|
||||||
coverImagePickerOpen: false,
|
coverImagePickerOpen: false,
|
||||||
},
|
},
|
||||||
() => NativeModules.UtilityModule.openDocumentPicker('image/*')
|
() => this.checkStoragePermission(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -574,7 +589,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
channelNameUserEdited: false,
|
channelNameUserEdited: false,
|
||||||
newChannelTitle: '',
|
newChannelTitle: '',
|
||||||
newChannelName: '',
|
newChannelName: '',
|
||||||
newChannelBid: 0.1,
|
newChannelBid: 0.01,
|
||||||
addingChannel: false,
|
addingChannel: false,
|
||||||
creatingChannel: false,
|
creatingChannel: false,
|
||||||
newChannelNameError: '',
|
newChannelNameError: '',
|
||||||
|
@ -672,7 +687,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: true }
|
{ cancelable: true },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -748,8 +763,52 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
this.addOrRemoveItem(channel);
|
this.addOrRemoveItem(channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkStoragePermission = () => {
|
||||||
|
// check if we the permission to write to external storage has been granted
|
||||||
|
NativeModules.UtilityModule.canReadWriteStorage().then(canReadWrite => {
|
||||||
|
if (!canReadWrite) {
|
||||||
|
// request permission
|
||||||
|
NativeModules.UtilityModule.requestStoragePermission();
|
||||||
|
} else {
|
||||||
|
NativeModules.UtilityModule.openDocumentPicker('image/*');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleStoragePermissionGranted = () => {
|
||||||
|
// update the configured download folder
|
||||||
|
NativeModules.UtilityModule.getDownloadDirectory().then(downloadDirectory => {
|
||||||
|
Lbry.settings_set({
|
||||||
|
key: 'download_dir',
|
||||||
|
value: downloadDirectory,
|
||||||
|
})
|
||||||
|
.then(() => {})
|
||||||
|
.catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
// open picker for images
|
||||||
|
NativeModules.UtilityModule.openDocumentPicker('image/*');
|
||||||
|
};
|
||||||
|
|
||||||
|
handleStoragePermissionRefused = () => {
|
||||||
|
const { notify } = this.props;
|
||||||
|
notify({
|
||||||
|
message: __('Pictures from your device cannot be used because the permission to read storage was not granted.'),
|
||||||
|
isError: true,
|
||||||
|
});
|
||||||
|
this.setState({ avatarImagePickerOpen: false, coverImagePickerOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { abandoningClaimIds, balance, fetchingChannels, updatingChannel, channels = [], navigation } = this.props;
|
const {
|
||||||
|
abandoningClaimIds,
|
||||||
|
balance,
|
||||||
|
fetchingChannels,
|
||||||
|
sdkReady,
|
||||||
|
updatingChannel,
|
||||||
|
channels = [],
|
||||||
|
navigation,
|
||||||
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
autoStyle,
|
autoStyle,
|
||||||
autoStyles,
|
autoStyles,
|
||||||
|
@ -773,6 +832,19 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
|
|
||||||
const hasChannels = channels && channels.length > 0;
|
const hasChannels = channels && channels.length > 0;
|
||||||
|
|
||||||
|
if (!sdkReady) {
|
||||||
|
return (
|
||||||
|
<View style={channelCreatorStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
<EmptyStateView
|
||||||
|
message={__(
|
||||||
|
'The background service is still initializing. You can still explore and watch content during the initialization process.',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={channelCreatorStyle.container}>
|
<View style={channelCreatorStyle.container}>
|
||||||
<UriBar
|
<UriBar
|
||||||
|
@ -830,9 +902,9 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
>
|
>
|
||||||
<View style={[channelCreatorStyle.channelListAvatar, itemAutoStyle]}>
|
<View style={[channelCreatorStyle.channelListAvatar, itemAutoStyle]}>
|
||||||
{itemThumbnailUrl && (
|
{itemThumbnailUrl && (
|
||||||
<Image
|
<FastImage
|
||||||
style={channelCreatorStyle.avatarImage}
|
style={channelCreatorStyle.avatarImage}
|
||||||
resizeMode={'cover'}
|
resizeMode={FastImage.resizeMode.cover}
|
||||||
source={{ uri: itemThumbnailUrl }}
|
source={{ uri: itemThumbnailUrl }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -867,7 +939,7 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
style={channelCreatorStyle.coverImage}
|
style={channelCreatorStyle.coverImage}
|
||||||
resizeMode={'cover'}
|
resizeMode={'cover'}
|
||||||
source={
|
source={
|
||||||
coverImageUrl !== null && coverImageUrl.trim().length > 0
|
!!coverImageUrl && coverImageUrl.trim().length > 0
|
||||||
? { uri: coverImageUrl }
|
? { uri: coverImageUrl }
|
||||||
: require('../../assets/default_channel_cover.png')
|
: require('../../assets/default_channel_cover.png')
|
||||||
}
|
}
|
||||||
|
@ -886,14 +958,15 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
|
|
||||||
<View style={[channelCreatorStyle.avatarImageContainer, autoStyle]}>
|
<View style={[channelCreatorStyle.avatarImageContainer, autoStyle]}>
|
||||||
<TouchableOpacity style={channelCreatorStyle.avatarTouchArea} onPress={this.onAvatarImagePress}>
|
<TouchableOpacity style={channelCreatorStyle.avatarTouchArea} onPress={this.onAvatarImagePress}>
|
||||||
{thumbnailUrl !== null && thumbnailUrl.trim().length > 0 && (
|
{!!thumbnailUrl && thumbnailUrl.trim().length > 0 && (
|
||||||
<Image
|
<Image
|
||||||
style={channelCreatorStyle.avatarImage}
|
style={channelCreatorStyle.avatarImage}
|
||||||
resizeMode={'cover'}
|
resizeMode={'cover'}
|
||||||
source={{ uri: thumbnailUrl }}
|
source={{ uri: thumbnailUrl }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(thumbnailUrl === null || thumbnailUrl.trim().length === 0) && newChannelName.length > 0 && (
|
{(!!thumbnailUrl || (!!thumbnailUrl && thumbnailUrl.trim().length === 0)) &&
|
||||||
|
newChannelName.length > 0 && (
|
||||||
<Text style={channelIconStyle.autothumbCharacter}>
|
<Text style={channelIconStyle.autothumbCharacter}>
|
||||||
{newChannelName.substring(0, 1).toUpperCase()}
|
{newChannelName.substring(0, 1).toUpperCase()}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -960,11 +1033,19 @@ export default class ChannelCreator extends React.PureComponent {
|
||||||
style={channelCreatorStyle.bidAmountInput}
|
style={channelCreatorStyle.bidAmountInput}
|
||||||
value={String(newChannelBid)}
|
value={String(newChannelBid)}
|
||||||
onChangeText={this.handleNewChannelBidChange}
|
onChangeText={this.handleNewChannelBidChange}
|
||||||
|
onFocus={() => this.setState({ creditsInputFocused: true })}
|
||||||
|
onBlur={() => this.setState({ creditsInputFocused: false })}
|
||||||
placeholder={'0.00'}
|
placeholder={'0.00'}
|
||||||
keyboardType={'number-pad'}
|
keyboardType={'number-pad'}
|
||||||
underlineColorAndroid={Colors.NextLbryGreen}
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
/>
|
/>
|
||||||
<Text style={channelCreatorStyle.currency}>LBC</Text>
|
<Text style={channelCreatorStyle.currency}>LBC</Text>
|
||||||
|
<View style={channelCreatorStyle.balance}>
|
||||||
|
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
|
||||||
|
{this.state.creditsInputFocused && (
|
||||||
|
<Text style={channelCreatorStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text style={channelCreatorStyle.helpText}>
|
<Text style={channelCreatorStyle.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.')}
|
||||||
|
|
|
@ -11,9 +11,9 @@ import {
|
||||||
selectSubscriptionClaims,
|
selectSubscriptionClaims,
|
||||||
selectUnreadSubscriptions,
|
selectUnreadSubscriptions,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import { doPushDrawerStack } from 'redux/actions/drawer';
|
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { doSetClientSetting, doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
|
import { doSetClientSetting, doSetSortByItem, doSetTimeItem } from 'redux/actions/settings';
|
||||||
import { makeSelectClientSetting, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectSdkReady, selectSortByItem, selectTimeItem } from 'redux/selectors/settings';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import DiscoverPage from './view';
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ const select = state => ({
|
||||||
followedTags: selectFollowedTags(state),
|
followedTags: selectFollowedTags(state),
|
||||||
ratingReminderDisabled: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED)(state),
|
ratingReminderDisabled: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_DISABLED)(state),
|
||||||
ratingReminderLastShown: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN)(state),
|
ratingReminderLastShown: makeSelectClientSetting(Constants.SETTING_RATING_REMINDER_LAST_SHOWN)(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
sortByItem: selectSortByItem(state),
|
sortByItem: selectSortByItem(state),
|
||||||
timeItem: selectTimeItem(state),
|
timeItem: selectTimeItem(state),
|
||||||
unreadSubscriptions: selectUnreadSubscriptions(state),
|
unreadSubscriptions: selectUnreadSubscriptions(state),
|
||||||
|
@ -41,11 +42,9 @@ const perform = dispatch => ({
|
||||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_DISCOVER)),
|
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_DISCOVER)),
|
||||||
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
|
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
setSortByItem: item => dispatch(doSetSortByItem(item)),
|
setSortByItem: item => dispatch(doSetSortByItem(item)),
|
||||||
setTimeItem: item => dispatch(doSetTimeItem(item)),
|
setTimeItem: item => dispatch(doSetTimeItem(item)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(DiscoverPage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(DiscoverPage);
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import Link from 'component/link';
|
import Link from 'component/link';
|
||||||
import ModalTagSelector from 'component/modalTagSelector';
|
import ModalTagSelector from 'component/modalTagSelector';
|
||||||
import ModalPicker from 'component/modalPicker';
|
import ModalPicker from 'component/modalPicker';
|
||||||
|
import SdkLoadingStatus from 'component/sdkLoadingStatus';
|
||||||
import UriBar from 'component/uriBar';
|
import UriBar from 'component/uriBar';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
@ -37,29 +38,6 @@ class DiscoverPage extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// Track the total time taken if this is the first launch
|
|
||||||
AsyncStorage.getItem('firstLaunchTime').then(startTime => {
|
|
||||||
if (startTime !== null && !isNaN(parseInt(startTime, 10))) {
|
|
||||||
// We don't need this value anymore once we've retrieved it
|
|
||||||
AsyncStorage.removeItem('firstLaunchTime');
|
|
||||||
|
|
||||||
// We know this is the first app launch because firstLaunchTime is set and it"s a valid number
|
|
||||||
const start = parseInt(startTime, 10);
|
|
||||||
const now = moment().unix();
|
|
||||||
const delta = now - start;
|
|
||||||
AsyncStorage.getItem('firstLaunchSuspended').then(suspended => {
|
|
||||||
AsyncStorage.removeItem('firstLaunchSuspended');
|
|
||||||
const appSuspended = suspended === 'true';
|
|
||||||
if (NativeModules.Firebase) {
|
|
||||||
NativeModules.Firebase.track('first_run_time', {
|
|
||||||
total_seconds: delta,
|
|
||||||
app_suspended: appSuspended,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { sortByItem, fetchRewardedContent, fileList, followedTags } = this.props;
|
const { sortByItem, fetchRewardedContent, fileList, followedTags } = this.props;
|
||||||
|
|
||||||
this.buildTagCollection(followedTags);
|
this.buildTagCollection(followedTags);
|
||||||
|
@ -86,9 +64,11 @@ class DiscoverPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onComponentFocused = () => {
|
onComponentFocused = () => {
|
||||||
const { pushDrawerStack } = this.props;
|
const { pushDrawerStack, setPlayerVisible, currentRoute } = this.props;
|
||||||
// pushDrawerStack();
|
pushDrawerStack();
|
||||||
|
|
||||||
NativeModules.Firebase.setCurrentScreen('Your tags');
|
NativeModules.Firebase.setCurrentScreen('Your tags');
|
||||||
|
setPlayerVisible();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSortByItemSelected = item => {
|
handleSortByItemSelected = item => {
|
||||||
|
@ -135,7 +115,7 @@ class DiscoverPage extends React.PureComponent {
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
__('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.',
|
||||||
),
|
),
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
@ -151,7 +131,7 @@ class DiscoverPage extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: false }
|
{ cancelable: false },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,26 +269,28 @@ class DiscoverPage extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { navigation, sortByItem, timeItem } = this.props;
|
const { currentRoute, navigation, sdkReady, sortByItem, timeItem } = this.props;
|
||||||
const { orderBy, showModalTagSelector, showSortPicker, showTimePicker } = this.state;
|
const { showModalTagSelector, showSortPicker, showTimePicker } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={discoverStyle.container}>
|
<View style={discoverStyle.container}>
|
||||||
<UriBar navigation={navigation} belowOverlay={showModalTagSelector} />
|
<UriBar navigation={navigation} belowOverlay={showModalTagSelector} />
|
||||||
<SectionList
|
{currentRoute !== Constants.ROUTE_FILE && currentRoute !== Constants.DRAWER_ROUTE_FILE_VIEW && (
|
||||||
ListHeaderComponent={this.sectionListHeader}
|
<SectionList
|
||||||
ListFooterComponent={this.sectionListFooter}
|
ListHeaderComponent={this.sectionListHeader}
|
||||||
style={discoverStyle.scrollContainer}
|
ListFooterComponent={this.sectionListFooter}
|
||||||
contentContainerStyle={discoverStyle.scrollPadding}
|
style={discoverStyle.scrollContainer}
|
||||||
initialNumToRender={4}
|
contentContainerStyle={discoverStyle.scrollPadding}
|
||||||
maxToRenderPerBatch={4}
|
initialNumToRender={4}
|
||||||
removeClippedSubviews
|
maxToRenderPerBatch={4}
|
||||||
renderItem={this.renderSectionListItem}
|
removeClippedSubviews
|
||||||
renderSectionHeader={this.renderSectionHeader}
|
renderItem={this.renderSectionListItem}
|
||||||
sections={this.buildSections()}
|
renderSectionHeader={this.renderSectionHeader}
|
||||||
keyExtractor={(item, index) => item}
|
sections={this.buildSections()}
|
||||||
/>
|
keyExtractor={(item, index) => item}
|
||||||
{!showModalTagSelector && !showSortPicker && !showTimePicker && (
|
/>
|
||||||
|
)}
|
||||||
|
{sdkReady && !showModalTagSelector && !showSortPicker && !showTimePicker && (
|
||||||
<FloatingWalletBalance navigation={navigation} />
|
<FloatingWalletBalance navigation={navigation} />
|
||||||
)}
|
)}
|
||||||
{showModalTagSelector && (
|
{showModalTagSelector && (
|
||||||
|
@ -335,6 +317,8 @@ class DiscoverPage extends React.PureComponent {
|
||||||
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
|
items={Constants.CLAIM_SEARCH_TIME_ITEMS}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!sdkReady && <SdkLoadingStatus />}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,17 @@ import {
|
||||||
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { doDeleteFile } from 'redux/actions/file';
|
import { doDeleteFile } from 'redux/actions/file';
|
||||||
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import DownloadsPage from './view';
|
import DownloadsPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
claims: selectMyClaimsWithoutChannels(state),
|
claims: selectMyClaimsWithoutChannels(state),
|
||||||
currentRoute: selectCurrentRoute(state),
|
currentRoute: selectCurrentRoute(state),
|
||||||
fileInfos: selectFileInfosDownloaded(state),
|
|
||||||
downloadedUris: selectDownloadedUris(state),
|
downloadedUris: selectDownloadedUris(state),
|
||||||
|
fileInfos: selectFileInfosDownloaded(state),
|
||||||
fetching: selectIsFetchingFileList(state) || selectIsFetchingClaimListMine(state),
|
fetching: selectIsFetchingFileList(state) || selectIsFetchingClaimListMine(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
@ -32,7 +34,4 @@ const perform = dispatch => ({
|
||||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(DownloadsPage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(DownloadsPage);
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ class DownloadsPage extends React.PureComponent {
|
||||||
const { claims, fileInfos } = this.props;
|
const { claims, fileInfos } = this.props;
|
||||||
const claimUris = claims.map(claim => normalizeURI(`${claim.name}#${claim.claim_id}`));
|
const claimUris = claims.map(claim => normalizeURI(`${claim.name}#${claim.claim_id}`));
|
||||||
return fileInfos.filter(
|
return fileInfos.filter(
|
||||||
fileInfo => !claimUris.includes(normalizeURI(`${fileInfo.claim_name}#${fileInfo.claim_id}`))
|
fileInfo => !claimUris.includes(normalizeURI(`${fileInfo.claim_name}#${fileInfo.claim_id}`)),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,7 +125,11 @@ class DownloadsPage extends React.PureComponent {
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
const uris = Object.keys(selectedClaimsMap);
|
const uris = Object.keys(selectedClaimsMap);
|
||||||
uris.forEach(uri => {
|
uris.forEach(uri => {
|
||||||
const { txid, nout } = selectedClaimsMap[uri];
|
const { txid, nout, name, claim_id: claimId } = selectedClaimsMap[uri];
|
||||||
|
if (name && claimId) {
|
||||||
|
NativeModules.UtilityModule.deleteDownload(normalizeURI(`${name}#${claimId}`));
|
||||||
|
}
|
||||||
|
|
||||||
deleteFile(`${txid}:${nout}`, true);
|
deleteFile(`${txid}:${nout}`, true);
|
||||||
});
|
});
|
||||||
this.onExitSelectionMode();
|
this.onExitSelectionMode();
|
||||||
|
@ -135,16 +139,29 @@ class DownloadsPage extends React.PureComponent {
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
cancelable: true,
|
cancelable: true,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fetching, claims, downloadedUris, fileInfos, navigation } = this.props;
|
const { downloadedUris, fetching, navigation, sdkReady } = this.props;
|
||||||
const { selectionMode, selectedUris } = this.state;
|
const { selectionMode, selectedUris } = this.state;
|
||||||
const filteredUris = this.getFilteredUris();
|
const filteredUris = this.getFilteredUris();
|
||||||
const hasDownloads = filteredUris && filteredUris.length > 0;
|
const hasDownloads = filteredUris && filteredUris.length > 0;
|
||||||
|
|
||||||
|
if (!sdkReady) {
|
||||||
|
return (
|
||||||
|
<View style={downloadsStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
<EmptyStateView
|
||||||
|
message={__(
|
||||||
|
'The background service is still initializing. You can still explore and watch content during the initialization process.',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={downloadsStyle.container}>
|
<View style={downloadsStyle.container}>
|
||||||
<UriBar
|
<UriBar
|
||||||
|
@ -184,7 +201,7 @@ class DownloadsPage extends React.PureComponent {
|
||||||
this.handleSelectItem(item, claim);
|
this.handleSelectItem(item, claim);
|
||||||
} else {
|
} else {
|
||||||
// TODO: when shortUrl is available for my claims, navigate to that URL instead
|
// TODO: when shortUrl is available for my claims, navigate to that URL instead
|
||||||
navigateToUri(navigation, item, { autoplay: true });
|
navigateToUri(navigation, item, { autoplay: true }, false, null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onLongPress={claim => this.handleItemLongPress(item, claim)}
|
onLongPress={claim => this.handleItemLongPress(item, claim)}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
doPurchaseUri,
|
doPurchaseUri,
|
||||||
doDeletePurchasedUri,
|
doDeletePurchasedUri,
|
||||||
doResolveUri,
|
doResolveUri,
|
||||||
doSearch,
|
doResolveUris,
|
||||||
doSendTip,
|
doSendTip,
|
||||||
doToast,
|
doToast,
|
||||||
makeSelectIsUriResolving,
|
makeSelectIsUriResolving,
|
||||||
|
@ -18,7 +18,6 @@ import {
|
||||||
makeSelectContentPositionForUri,
|
makeSelectContentPositionForUri,
|
||||||
makeSelectContentTypeForUri,
|
makeSelectContentTypeForUri,
|
||||||
makeSelectMetadataForUri,
|
makeSelectMetadataForUri,
|
||||||
makeSelectRecommendedContentForUri,
|
|
||||||
makeSelectStreamingUrlForUri,
|
makeSelectStreamingUrlForUri,
|
||||||
makeSelectThumbnailForUri,
|
makeSelectThumbnailForUri,
|
||||||
makeSelectTitleForUri,
|
makeSelectTitleForUri,
|
||||||
|
@ -28,25 +27,21 @@ import {
|
||||||
selectPurchasedUris,
|
selectPurchasedUris,
|
||||||
selectFailedPurchaseUris,
|
selectFailedPurchaseUris,
|
||||||
selectPurchaseUriErrorMessage,
|
selectPurchaseUriErrorMessage,
|
||||||
selectIsSearching,
|
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
doClaimEligiblePurchaseRewards,
|
doClaimEligiblePurchaseRewards,
|
||||||
doFetchCostInfoForUri,
|
doFetchCostInfoForUri,
|
||||||
|
doFetchViewCount,
|
||||||
makeSelectCostInfoForUri,
|
makeSelectCostInfoForUri,
|
||||||
|
makeSelectViewCountForUri,
|
||||||
selectRewardContentClaimIds,
|
selectRewardContentClaimIds,
|
||||||
selectBlackListedOutpoints,
|
selectBlackListedOutpoints,
|
||||||
} from 'lbryinc';
|
} from 'lbryinc';
|
||||||
import {
|
import { doDeleteFile, doStopDownloadingFile } from 'redux/actions/file';
|
||||||
doStartDownload,
|
|
||||||
doUpdateDownload,
|
|
||||||
doCompleteDownload,
|
|
||||||
doDeleteFile,
|
|
||||||
doStopDownloadingFile,
|
|
||||||
} from 'redux/actions/file';
|
|
||||||
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { doToggleFullscreenMode } from 'redux/actions/settings';
|
import { doToggleFullscreenMode } from 'redux/actions/settings';
|
||||||
import { selectDrawerStack } from 'redux/selectors/drawer';
|
import { selectDrawerStack, makeSelectPlayerVisible } from 'redux/selectors/drawer';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -66,16 +61,17 @@ const select = (state, props) => {
|
||||||
fileInfo: makeSelectFileInfoForUri(contentUri)(state),
|
fileInfo: makeSelectFileInfoForUri(contentUri)(state),
|
||||||
rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps),
|
rewardedContentClaimIds: selectRewardContentClaimIds(state, selectProps),
|
||||||
channelUri: makeSelectChannelForClaimUri(contentUri, true)(state),
|
channelUri: makeSelectChannelForClaimUri(contentUri, true)(state),
|
||||||
|
isPlayerVisible: makeSelectPlayerVisible(uri)(state), // use navigation uri for this selector
|
||||||
position: makeSelectContentPositionForUri(contentUri)(state),
|
position: makeSelectContentPositionForUri(contentUri)(state),
|
||||||
purchasedUris: selectPurchasedUris(state),
|
purchasedUris: selectPurchasedUris(state),
|
||||||
failedPurchaseUris: selectFailedPurchaseUris(state),
|
failedPurchaseUris: selectFailedPurchaseUris(state),
|
||||||
myClaimUris: selectMyClaimUrisWithoutChannels(state),
|
myClaimUris: selectMyClaimUrisWithoutChannels(state),
|
||||||
purchaseUriErrorMessage: selectPurchaseUriErrorMessage(state),
|
purchaseUriErrorMessage: selectPurchaseUriErrorMessage(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
streamingUrl: makeSelectStreamingUrlForUri(contentUri)(state),
|
streamingUrl: makeSelectStreamingUrlForUri(contentUri)(state),
|
||||||
thumbnail: makeSelectThumbnailForUri(contentUri)(state),
|
thumbnail: makeSelectThumbnailForUri(contentUri)(state),
|
||||||
title: makeSelectTitleForUri(contentUri)(state),
|
title: makeSelectTitleForUri(contentUri)(state),
|
||||||
recommendedContent: makeSelectRecommendedContentForUri(contentUri)(state),
|
viewCount: makeSelectViewCountForUri(contentUri)(state),
|
||||||
isSearchingRecommendContent: selectIsSearching(state),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -88,7 +84,8 @@ const perform = dispatch => ({
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
|
||||||
|
fetchViewCount: claimId => dispatch(doFetchViewCount(claimId)),
|
||||||
fileGet: (uri, saveFile) => dispatch(doFileGet(uri, saveFile)),
|
fileGet: (uri, saveFile) => dispatch(doFileGet(uri, saveFile)),
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
popDrawerStack: () => dispatch(doPopDrawerStack()),
|
||||||
|
@ -96,18 +93,12 @@ const perform = dispatch => ({
|
||||||
purchaseUri: (uri, costInfo, saveFile) => dispatch(doPurchaseUri(uri, costInfo, saveFile)),
|
purchaseUri: (uri, costInfo, saveFile) => dispatch(doPurchaseUri(uri, costInfo, saveFile)),
|
||||||
deletePurchasedUri: uri => dispatch(doDeletePurchasedUri(uri)),
|
deletePurchasedUri: uri => dispatch(doDeletePurchasedUri(uri)),
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
searchRecommended: query => dispatch(doSearch(query, 20, undefined, true)),
|
resolveUris: uris => dispatch(doResolveUris(uris)),
|
||||||
sendTip: (amount, claimId, isSupport, successCallback, errorCallback) =>
|
sendTip: (amount, claimId, isSupport, successCallback, errorCallback) =>
|
||||||
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
|
dispatch(doSendTip(amount, claimId, isSupport, successCallback, errorCallback)),
|
||||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(true)),
|
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
|
||||||
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
|
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
|
||||||
startDownload: (uri, outpoint, fileInfo) => dispatch(doStartDownload(uri, outpoint, fileInfo)),
|
|
||||||
updateDownload: (uri, outpoint, fileInfo, progress) => dispatch(doUpdateDownload(uri, outpoint, fileInfo, progress)),
|
|
||||||
completeDownload: (uri, outpoint, fileInfo) => dispatch(doCompleteDownload(uri, outpoint, fileInfo)),
|
|
||||||
toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)),
|
toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(FilePage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(FilePage);
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -228,7 +228,13 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
handleContinuePressed = () => {
|
handleContinuePressed = () => {
|
||||||
const { notify, user, hasSyncedWallet } = this.props;
|
const { notify, user, hasSyncedWallet } = this.props;
|
||||||
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
const pageIndex = FirstRunScreen.pages.indexOf(this.state.currentPage);
|
||||||
if (Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage) {
|
|
||||||
|
if (Constants.FIRST_RUN_PAGE_WELCOME === this.state.currentPage) {
|
||||||
|
// only show the welcome screen on first run
|
||||||
|
this.closeFinalPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if (Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage) {
|
||||||
// do apply sync to check if the password is valid
|
// do apply sync to check if the password is valid
|
||||||
if (hasSyncedWallet) {
|
if (hasSyncedWallet) {
|
||||||
this.checkWalletPassword();
|
this.checkWalletPassword();
|
||||||
|
@ -256,7 +262,7 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
} else {
|
} else {
|
||||||
this.showNextPage();
|
this.showNextPage();
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
};
|
};
|
||||||
|
|
||||||
handleEmailCollectPageContinue() {
|
handleEmailCollectPageContinue() {
|
||||||
|
@ -309,7 +315,7 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
onEmailChanged = email => {
|
onEmailChanged = email => {
|
||||||
this.setState({ email });
|
this.setState({ email });
|
||||||
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) {
|
if (Constants.FIRST_RUN_PAGE_EMAIL_COLLECT === this.state.currentPage) {
|
||||||
this.setState({ showSkip: !email || email.trim().length === 0 });
|
this.setState({ showSkip: !email || typeof email !== 'string' || email.trim().length === 0 });
|
||||||
} else {
|
} else {
|
||||||
this.setState({ showSkip: false });
|
this.setState({ showSkip: false });
|
||||||
}
|
}
|
||||||
|
@ -500,7 +506,12 @@ class FirstRunScreen extends React.PureComponent {
|
||||||
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
|
{Constants.FIRST_RUN_PAGE_SKIP_ACCOUNT !== this.state.currentPage &&
|
||||||
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY !== this.state.currentPage && (
|
Constants.FIRST_RUN_PAGE_EMAIL_VERIFY !== this.state.currentPage && (
|
||||||
<Text style={firstRunStyle.buttonText}>
|
<Text style={firstRunStyle.buttonText}>
|
||||||
{Constants.FIRST_RUN_PAGE_WALLET === this.state.currentPage ? __('Use LBRY') : __('Continue')} »
|
{[Constants.FIRST_RUN_PAGE_WALLET, Constants.FIRST_RUN_PAGE_WELCOME].includes(
|
||||||
|
this.state.currentPage
|
||||||
|
)
|
||||||
|
? __('Use LBRY')
|
||||||
|
: __('Continue')}{' '}
|
||||||
|
»
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
42
src/page/invites/index.js
Normal file
42
src/page/invites/index.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectMyChannelClaims, selectFetchingMyChannels, doFetchChannelListMine, doToast } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
selectReferralReward,
|
||||||
|
selectUserInvitesRemaining,
|
||||||
|
selectUserInviteNewIsPending,
|
||||||
|
selectUserInviteNewErrorMessage,
|
||||||
|
selectUserInviteReferralLink,
|
||||||
|
selectUserInviteReferralCode,
|
||||||
|
selectUserInvitees,
|
||||||
|
selectUserInviteStatusIsPending,
|
||||||
|
doFetchInviteStatus,
|
||||||
|
doUserInviteNew,
|
||||||
|
} from 'lbryinc';
|
||||||
|
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
|
import InvitesPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
channels: selectMyChannelClaims(state),
|
||||||
|
errorMessage: selectUserInviteNewErrorMessage(state),
|
||||||
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
|
fetchingInvitees: selectUserInviteStatusIsPending(state),
|
||||||
|
invitees: selectUserInvitees(state),
|
||||||
|
invitesRemaining: selectUserInvitesRemaining(state),
|
||||||
|
isPending: selectUserInviteNewIsPending(state),
|
||||||
|
referralCode: selectUserInviteReferralCode(state),
|
||||||
|
referralReward: selectReferralReward(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine(1, 99999, true)),
|
||||||
|
fetchInviteStatus: () => dispatch(doFetchInviteStatus()),
|
||||||
|
inviteNew: email => dispatch(doUserInviteNew(email)),
|
||||||
|
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_INVITES)),
|
||||||
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
|
notify: data => dispatch(doToast(data)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(InvitesPage);
|
253
src/page/invites/view.js
Normal file
253
src/page/invites/view.js
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Lbry, parseURI } from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Clipboard,
|
||||||
|
NativeModules,
|
||||||
|
ScrollView,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import ChannelSelector from 'component/channelSelector';
|
||||||
|
import EmptyStateView from 'component/emptyStateView';
|
||||||
|
import UriBar from 'component/uriBar';
|
||||||
|
import invitesStyle from 'styles/invites';
|
||||||
|
import { fetchReferralCode, logPublish } from 'utils/helper';
|
||||||
|
|
||||||
|
class InvitesPage extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
channelName: null,
|
||||||
|
email: null,
|
||||||
|
inviteLink: null,
|
||||||
|
selectedChannel: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
// this.didFocusListener = navigation.addListener('didFocus', this.onComponentFocused);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.didFocusListener) {
|
||||||
|
this.didFocusListener.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onComponentFocused = () => {
|
||||||
|
const { fetchChannelListMine, fetchInviteStatus, pushDrawerStack, navigation, setPlayerVisible, user } = this.props;
|
||||||
|
|
||||||
|
pushDrawerStack();
|
||||||
|
setPlayerVisible();
|
||||||
|
NativeModules.Firebase.setCurrentScreen('Invites').then(result => {
|
||||||
|
fetchReferralCode(
|
||||||
|
response => {
|
||||||
|
if (response && response.length > 0) {
|
||||||
|
// only need to use the first referral code.
|
||||||
|
// inviteLink will be updated after channels are loaded (if the user has created at least one channel)
|
||||||
|
this.setState({ inviteLink: `https://lbry.tv/$/invite/${response[0]}` });
|
||||||
|
}
|
||||||
|
fetchChannelListMine();
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
fetchChannelListMine();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
fetchInviteStatus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.onComponentFocused();
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
logPublish(channel);
|
||||||
|
this.setState({ channelName, inviteLink: this.getLinkForChannel(channel) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getLinkForChannel = channel => {
|
||||||
|
const parsedUrl = channel.canonical_url ? parseURI(channel.canonical_url) : parseURI(channel.permanent_url);
|
||||||
|
const { claimId, claimName } = parsedUrl;
|
||||||
|
return `https://lbry.tv/$/invite/${claimName}:${claimId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleInviteEmailChange = text => {
|
||||||
|
this.setState({ email: text });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleInvitePress = () => {
|
||||||
|
const { inviteNew, notify } = this.props;
|
||||||
|
const { email } = this.state;
|
||||||
|
if (!email || email.indexOf('@') === -1) {
|
||||||
|
return notify({
|
||||||
|
message: __('Please enter a valid email address to send an invite to.'),
|
||||||
|
isError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteNew(email);
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
const { isPending: prevPending, notify } = this.props;
|
||||||
|
const { channels = [], isPending, errorMessage } = nextProps;
|
||||||
|
const { email } = this.state;
|
||||||
|
|
||||||
|
if (!this.state.channelName && channels && channels.length > 0) {
|
||||||
|
const firstChannel = channels[0];
|
||||||
|
logPublish(firstChannel);
|
||||||
|
this.setState({ channelName: firstChannel.name, inviteLink: this.getLinkForChannel(firstChannel) });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevPending && !isPending) {
|
||||||
|
if (errorMessage && errorMessage.trim().length > 0) {
|
||||||
|
notify({ message: errorMessage, isError: true });
|
||||||
|
} else {
|
||||||
|
notify({ message: __(`${email} was invited to the LBRY party!`) });
|
||||||
|
this.setState({ email: null });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInviteLinkPress = () => {
|
||||||
|
const { notify } = this.props;
|
||||||
|
Clipboard.setString(this.state.inviteLink);
|
||||||
|
notify({
|
||||||
|
message: __('Invite link copied'),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { fetchingInvitees, invitees, isPending, navigation, sdkReady } = this.props;
|
||||||
|
const { email } = this.state;
|
||||||
|
const hasInvitees = invitees && invitees.length > 0;
|
||||||
|
|
||||||
|
if (!sdkReady) {
|
||||||
|
return (
|
||||||
|
<View style={invitesStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
<EmptyStateView
|
||||||
|
message={__(
|
||||||
|
'The background service is still initializing. You can still explore and watch content during the initialization process.',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={invitesStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
|
||||||
|
<ScrollView style={invitesStyle.scrollContainer}>
|
||||||
|
<TouchableOpacity style={invitesStyle.rewardDriverCard} onPress={() => navigation.navigate('Rewards')}>
|
||||||
|
<Icon name="award" size={16} style={invitesStyle.rewardDriverIcon} />
|
||||||
|
<Text style={invitesStyle.rewardDriverText}>{__('Earn rewards for inviting your friends.')}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={invitesStyle.card}>
|
||||||
|
<Text style={invitesStyle.title}>{__('Invite Link')}</Text>
|
||||||
|
<Text style={invitesStyle.text}>
|
||||||
|
{__('Share this link with friends (or enemies) and get 20 LBC when they join lbry.tv')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text style={invitesStyle.subTitle}>{__('Your invite link')}</Text>
|
||||||
|
<View style={invitesStyle.row}>
|
||||||
|
<Text selectable numberOfLines={1} style={invitesStyle.inviteLink} onPress={this.handleInviteLinkPress}>
|
||||||
|
{this.state.inviteLink}
|
||||||
|
</Text>
|
||||||
|
<Button icon={'clipboard'} style={invitesStyle.button} onPress={this.handleInviteLinkPress} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={invitesStyle.customizeTitle}>{__('Customize invite link')}</Text>
|
||||||
|
<ChannelSelector
|
||||||
|
showAnonymous={false}
|
||||||
|
channelName={this.state.channelName}
|
||||||
|
onChannelChange={this.handleChannelChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={invitesStyle.card}>
|
||||||
|
<Text style={invitesStyle.title}>{__('Invite by Email')}</Text>
|
||||||
|
<Text style={invitesStyle.text}>
|
||||||
|
{__('Invite someone you know by email and earn 20 LBC when they join lbry.tv.')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
style={invitesStyle.emailInput}
|
||||||
|
editable={!isPending}
|
||||||
|
value={this.state.email}
|
||||||
|
onChangeText={this.handleInviteEmailChange}
|
||||||
|
placeholder={__('imaginary@friend.com')}
|
||||||
|
underlineColorAndroid={Colors.NextLbryGreen}
|
||||||
|
/>
|
||||||
|
<View style={invitesStyle.rightRow}>
|
||||||
|
{isPending && (
|
||||||
|
<ActivityIndicator size={'small'} color={Colors.NextLbryGreen} style={invitesStyle.loading} />
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
disabled={!email || email.indexOf('@') === -1 || isPending}
|
||||||
|
style={invitesStyle.button}
|
||||||
|
text={__('Invite')}
|
||||||
|
onPress={this.handleInvitePress}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[invitesStyle.card, invitesStyle.lastCard]}>
|
||||||
|
<View style={invitesStyle.titleRow}>
|
||||||
|
<Text style={invitesStyle.titleCol}>{__('Invite History')}</Text>
|
||||||
|
{fetchingInvitees && <ActivityIndicator size={'small'} color={Colors.NextLbryGreen} />}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={invitesStyle.text}>
|
||||||
|
{__(
|
||||||
|
'Earn 20 LBC for inviting a friend, an enemy, a frenemy, or an enefriend. Everyone needs content freedom.',
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={invitesStyle.invitees}>
|
||||||
|
{hasInvitees && (
|
||||||
|
<View style={invitesStyle.inviteesHeader}>
|
||||||
|
<Text style={invitesStyle.emailHeader} numberOfLines={1}>
|
||||||
|
{__('Email')}
|
||||||
|
</Text>
|
||||||
|
<Text style={invitesStyle.rewardHeader} numberOfLines={1}>
|
||||||
|
{__('Reward')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{hasInvitees &&
|
||||||
|
invitees.map(invitee => (
|
||||||
|
<View key={invitee.email} style={invitesStyle.inviteeItem}>
|
||||||
|
<Text style={invitesStyle.inviteeEmail} numberOfLines={1}>
|
||||||
|
{invitee.email}
|
||||||
|
</Text>
|
||||||
|
<Text style={invitesStyle.rewardStatus} numberOfLines={1}>
|
||||||
|
{invitee.invite_reward_claimed && __('Claimed')}
|
||||||
|
{!invitee.invite_reward_claimed &&
|
||||||
|
(invitee.invite_reward_claimable ? __('Claimable') : __('Unclaimable'))}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InvitesPage;
|
28
src/page/liteFile/index.js
Normal file
28
src/page/liteFile/index.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectContentPositionForUri, selectBalance } from 'lbry-redux';
|
||||||
|
import { doClaimEligiblePurchaseRewards, makeSelectViewCountForUri, selectRewardContentClaimIds } from 'lbryinc';
|
||||||
|
import { doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
|
import { makeSelectPlayerVisible } from 'redux/selectors/drawer';
|
||||||
|
import { doToggleFullscreenMode } from 'redux/actions/settings';
|
||||||
|
import LiteFilePage from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const { uri, fullUri } = props.navigation.state.params;
|
||||||
|
const contentUri = fullUri || uri;
|
||||||
|
const selectProps = { uri: contentUri };
|
||||||
|
return {
|
||||||
|
balance: selectBalance(state),
|
||||||
|
isPlayerVisible: makeSelectPlayerVisible(uri)(state), // use navigation uri for this selector
|
||||||
|
position: makeSelectContentPositionForUri(contentUri)(state),
|
||||||
|
viewCount: makeSelectViewCountForUri(contentUri)(state),
|
||||||
|
rewardedContentClaimIds: selectRewardContentClaimIds(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = dispatch => ({
|
||||||
|
claimEligibleRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||||
|
setPlayerVisible: (visible, uri) => dispatch(doSetPlayerVisible(visible, uri)),
|
||||||
|
toggleFullscreenMode: mode => dispatch(doToggleFullscreenMode(mode)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(LiteFilePage);
|
297
src/page/liteFile/view.js
Normal file
297
src/page/liteFile/view.js
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Lbry, formatCredits, normalizeURI, parseURI, parseQueryParams } from 'lbry-redux';
|
||||||
|
import { Lbryio } from 'lbryinc';
|
||||||
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
Alert,
|
||||||
|
DeviceEventEmitter,
|
||||||
|
Dimensions,
|
||||||
|
Image,
|
||||||
|
Linking,
|
||||||
|
NativeModules,
|
||||||
|
Platform,
|
||||||
|
ScrollView,
|
||||||
|
StatusBar,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
TouchableWithoutFeedback,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
import UriBar from 'component/uriBar';
|
||||||
|
import Link from 'component/link';
|
||||||
|
import MediaPlayer from 'component/mediaPlayer';
|
||||||
|
import RelatedContent from 'component/relatedContent';
|
||||||
|
import filePageStyle from 'styles/filePage';
|
||||||
|
import { decode, formatLbryUrlForWeb, navigateToUri } from 'utils/helper';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
|
import uriBarStyle from 'styles/uriBar';
|
||||||
|
import { NavigationActions, StackActions } from 'react-navigation';
|
||||||
|
|
||||||
|
// This page will only be used for playing audio / video content from a remote stream URL
|
||||||
|
class LiteFilePage extends React.PureComponent {
|
||||||
|
playerBackground = null;
|
||||||
|
|
||||||
|
scrollView = null;
|
||||||
|
|
||||||
|
player = null;
|
||||||
|
|
||||||
|
startTime = null;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
channelName: null,
|
||||||
|
channelUrl: null,
|
||||||
|
fileViewLogged: false,
|
||||||
|
fullscreenMode: false,
|
||||||
|
playbackStarted: false,
|
||||||
|
playerHeight: null,
|
||||||
|
isLandscape: false,
|
||||||
|
sdkReady: false, // TODO: progressively enable features (e.g. tip) when sdk is ready
|
||||||
|
showRecommended: false,
|
||||||
|
title: null,
|
||||||
|
viewCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
checkOrientation = () => {
|
||||||
|
if (this.state.fullscreenMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenDimension = Dimensions.get('window');
|
||||||
|
const screenWidth = screenDimension.width;
|
||||||
|
const screenHeight = screenDimension.height;
|
||||||
|
const isLandscape = screenWidth > screenHeight;
|
||||||
|
this.setState({ isLandscape });
|
||||||
|
|
||||||
|
if (!this.playerBackground) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLandscape) {
|
||||||
|
this.playerBackground.setNativeProps({
|
||||||
|
height: screenHeight - StyleSheet.flatten(uriBarStyle.uriContainer).height,
|
||||||
|
});
|
||||||
|
} else if (this.state.playerBgHeight > 0) {
|
||||||
|
this.playerBackground.setNativeProps({ height: this.state.playerBgHeight });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleFullscreenToggle = isFullscreen => {
|
||||||
|
const { toggleFullscreenMode } = this.props;
|
||||||
|
toggleFullscreenMode(isFullscreen);
|
||||||
|
|
||||||
|
if (isFullscreen) {
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// show the navigation bar (on devices that have the soft navigation bar)
|
||||||
|
NativeModules.UtilityModule.showNavigationBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ fullscreenMode: isFullscreen });
|
||||||
|
StatusBar.setHidden(isFullscreen);
|
||||||
|
};
|
||||||
|
|
||||||
|
getStreamUrl = url => {
|
||||||
|
const { claimName, claimId } = parseURI(url);
|
||||||
|
return `https://player.lbry.tv/content/claims/${claimName}/${claimId}/stream`;
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSharePress = url => {
|
||||||
|
const shareUrl = Constants.SHARE_BASE_URL + formatLbryUrlForWeb(url);
|
||||||
|
NativeModules.UtilityModule.shareUrl(shareUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOpenUrl = url => {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
Alert.alert(
|
||||||
|
__('Stop watching?'),
|
||||||
|
'The LBRY service is still loading stuff in the background. Would you like to continue?',
|
||||||
|
[
|
||||||
|
{ text: __('No') },
|
||||||
|
{
|
||||||
|
text: __('Yes'),
|
||||||
|
onPress: () => {
|
||||||
|
const resetAction = StackActions.reset({
|
||||||
|
index: 0,
|
||||||
|
actions: [NavigationActions.navigate({ routeName: 'Splash', params: { resetUrl: url } })],
|
||||||
|
});
|
||||||
|
navigation.dispatch(resetAction);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.startTime = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const { navigation } = this.props;
|
||||||
|
const { uri } = navigation.state.params;
|
||||||
|
|
||||||
|
if (!this.state.title) {
|
||||||
|
const params = parseQueryParams(uri);
|
||||||
|
const { channelUrl, contentTitle } = params;
|
||||||
|
const channelName = channelUrl ? parseURI(decode(channelUrl)).claimName : null;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
title: decode(contentTitle),
|
||||||
|
channelUrl,
|
||||||
|
channelName,
|
||||||
|
showRecommended: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { navigation, rewardedContentClaimIds } = this.props;
|
||||||
|
const { channelName, channelUrl, title, sdkReady, viewCount } = this.state;
|
||||||
|
const { uri } = navigation.state.params;
|
||||||
|
const { claimName, claimId } = parseURI(uri);
|
||||||
|
const isRewardContent = rewardedContentClaimIds.includes(claimId);
|
||||||
|
|
||||||
|
const playerBgStyle = [filePageStyle.playerBackground, filePageStyle.containedPlayerBackground];
|
||||||
|
const fsPlayerBgStyle = [filePageStyle.playerBackground, filePageStyle.fullscreenPlayerBackground];
|
||||||
|
|
||||||
|
const playerStyle = [
|
||||||
|
filePageStyle.player,
|
||||||
|
this.state.isLandscape
|
||||||
|
? filePageStyle.containedPlayerLandscape
|
||||||
|
: this.state.fullscreenMode
|
||||||
|
? filePageStyle.fullscreenPlayer
|
||||||
|
: filePageStyle.containedPlayer,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={filePageStyle.pageContainer}>
|
||||||
|
{!this.state.fullscreenMode && <UriBar value={uri.split('?')[0]} navigation={navigation} />}
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={this.state.fullscreenMode ? filePageStyle.innerPageContainerFsMode : filePageStyle.innerPageContainer}
|
||||||
|
onLayout={this.checkOrientation}
|
||||||
|
>
|
||||||
|
<TouchableOpacity activeOpacity={0.5} style={filePageStyle.mediaContainer} />
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={playerBgStyle}
|
||||||
|
ref={ref => {
|
||||||
|
this.playerBackground = ref;
|
||||||
|
}}
|
||||||
|
onLayout={evt => {
|
||||||
|
if (!this.state.playerBgHeight) {
|
||||||
|
this.setState({ playerBgHeight: evt.nativeEvent.layout.height });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{this.state.fullscreenMode && <View style={fsPlayerBgStyle} />}
|
||||||
|
<MediaPlayer
|
||||||
|
assignPlayer={ref => {
|
||||||
|
this.player = ref;
|
||||||
|
}}
|
||||||
|
uri={uri}
|
||||||
|
source={this.getStreamUrl(uri)}
|
||||||
|
style={playerStyle}
|
||||||
|
autoPlay
|
||||||
|
onFullscreenToggled={this.handleFullscreenToggle}
|
||||||
|
onLayout={evt => {
|
||||||
|
if (!this.state.playerHeight) {
|
||||||
|
this.setState({ playerHeight: evt.nativeEvent.layout.height });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
style={filePageStyle.scrollContainer}
|
||||||
|
contentContainerStyle={filePageStyle.scrollContent}
|
||||||
|
keyboardShouldPersistTaps={'handled'}
|
||||||
|
ref={ref => {
|
||||||
|
this.scrollView = ref;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TouchableWithoutFeedback
|
||||||
|
style={filePageStyle.titleTouch}
|
||||||
|
onPress={() => this.setState({ showDescription: !this.state.showDescription })}
|
||||||
|
>
|
||||||
|
<View style={filePageStyle.titleArea}>
|
||||||
|
<View style={filePageStyle.titleRow}>
|
||||||
|
<Text style={filePageStyle.title} selectable>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
{isRewardContent && <Icon name="award" style={filePageStyle.rewardIcon} size={16} />}
|
||||||
|
</View>
|
||||||
|
<Text style={filePageStyle.viewCount}>
|
||||||
|
{viewCount === 1 && __('%view% view', { view: viewCount })}
|
||||||
|
{viewCount > 1 && __('%view% views', { view: viewCount })}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
|
|
||||||
|
<View style={filePageStyle.largeButtonsRow}>
|
||||||
|
<TouchableOpacity style={filePageStyle.largeButton} onPress={() => this.handleSharePress(uri)}>
|
||||||
|
<Icon name={'share-alt'} size={16} style={filePageStyle.largeButtonIcon} />
|
||||||
|
<Text style={filePageStyle.largeButtonText}>{__('Share')}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
{sdkReady && (
|
||||||
|
<TouchableOpacity
|
||||||
|
style={filePageStyle.largeButton}
|
||||||
|
onPress={() => this.setState({ showTipView: true })}
|
||||||
|
>
|
||||||
|
<Icon name={'gift'} size={16} style={filePageStyle.largeButtonIcon} />
|
||||||
|
<Text style={filePageStyle.largeButtonText}>{__('Tip')}</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={filePageStyle.channelRow}>
|
||||||
|
<View style={filePageStyle.publishInfo}>
|
||||||
|
{channelName && (
|
||||||
|
<Link
|
||||||
|
style={filePageStyle.channelName}
|
||||||
|
selectable
|
||||||
|
text={channelName}
|
||||||
|
numberOfLines={1}
|
||||||
|
ellipsizeMode={'tail'}
|
||||||
|
onPress={() => this.handleOpenUrl(channelUrl)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!channelName && (
|
||||||
|
<Text style={filePageStyle.anonChannelName} selectable ellipsizeMode={'tail'}>
|
||||||
|
{__('Anonymous')}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View onLayout={this.setRelatedContentPosition} />
|
||||||
|
|
||||||
|
{this.state.showRecommended && (
|
||||||
|
<RelatedContent
|
||||||
|
navigation={navigation}
|
||||||
|
claimId={claimId}
|
||||||
|
claimName={claimName}
|
||||||
|
title={title}
|
||||||
|
urlOpenHandler={this.handleOpenUrl}
|
||||||
|
uri={uri}
|
||||||
|
fullUri={uri}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LiteFilePage;
|
|
@ -1,6 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
doPublish,
|
doPublish,
|
||||||
|
doFetchClaimListMine,
|
||||||
doResolveUri,
|
doResolveUri,
|
||||||
doToast,
|
doToast,
|
||||||
doUpdatePublishForm,
|
doUpdatePublishForm,
|
||||||
|
@ -12,6 +13,7 @@ import { selectDrawerStack } from 'redux/selectors/drawer';
|
||||||
import { doUpdatePublishFormState, doClearPublishFormState, doPendingPublishSuccess } from 'redux/actions/form';
|
import { doUpdatePublishFormState, doClearPublishFormState, doPendingPublishSuccess } from 'redux/actions/form';
|
||||||
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { selectPublishFormState, selectHasPublishFormState } from 'redux/selectors/form';
|
import { selectPublishFormState, selectHasPublishFormState } from 'redux/selectors/form';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import PublishPage from './view';
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
@ -22,11 +24,13 @@ const select = state => ({
|
||||||
myClaims: selectMyClaims(state),
|
myClaims: selectMyClaims(state),
|
||||||
publishFormState: selectPublishFormState(state),
|
publishFormState: selectPublishFormState(state),
|
||||||
publishFormValues: selectPublishFormValues(state),
|
publishFormValues: selectPublishFormValues(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
clearPublishFormState: () => dispatch(doClearPublishFormState()),
|
clearPublishFormState: () => dispatch(doClearPublishFormState()),
|
||||||
|
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
||||||
pendingPublishSuccess: pendingClaim => dispatch(doPendingPublishSuccess(pendingClaim)),
|
pendingPublishSuccess: pendingClaim => dispatch(doPendingPublishSuccess(pendingClaim)),
|
||||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
updatePublishFormState: data => dispatch(doUpdatePublishFormState(data)),
|
updatePublishFormState: data => dispatch(doUpdatePublishFormState(data)),
|
||||||
|
@ -37,7 +41,4 @@ const perform = dispatch => ({
|
||||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(PublishPage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(PublishPage);
|
|
||||||
|
|
|
@ -16,9 +16,11 @@ import {
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { FlatGrid } from 'react-native-super-grid';
|
import { FlatGrid } from 'react-native-super-grid';
|
||||||
import {
|
import {
|
||||||
|
Lbry,
|
||||||
isNameValid,
|
isNameValid,
|
||||||
batchActions,
|
batchActions,
|
||||||
buildURI,
|
buildURI,
|
||||||
|
formatCredits,
|
||||||
normalizeURI,
|
normalizeURI,
|
||||||
parseURI,
|
parseURI,
|
||||||
regexInvalidURI,
|
regexInvalidURI,
|
||||||
|
@ -35,6 +37,7 @@ import Button from 'component/button';
|
||||||
import ChannelSelector from 'component/channelSelector';
|
import ChannelSelector from 'component/channelSelector';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
|
import EmptyStateView from 'component/emptyStateView';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||||
import Icon from 'react-native-vector-icons/FontAwesome5';
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
|
@ -45,7 +48,7 @@ import Tag from 'component/tag';
|
||||||
import TagSearch from 'component/tagSearch';
|
import TagSearch from 'component/tagSearch';
|
||||||
import UriBar from 'component/uriBar';
|
import UriBar from 'component/uriBar';
|
||||||
import publishStyle from 'styles/publish';
|
import publishStyle from 'styles/publish';
|
||||||
import { navigateToUri, logPublish, uploadImageAsset } from 'utils/helper';
|
import { navigateToUri, navigateBack, logPublish, uploadImageAsset } from 'utils/helper';
|
||||||
|
|
||||||
const languages = {
|
const languages = {
|
||||||
en: 'English',
|
en: 'English',
|
||||||
|
@ -83,6 +86,7 @@ class PublishPage extends React.PureComponent {
|
||||||
state = {
|
state = {
|
||||||
canPublish: true,
|
canPublish: true,
|
||||||
canUseCamera: false,
|
canUseCamera: false,
|
||||||
|
creditsInputFocused: false,
|
||||||
documentPickerOpen: false,
|
documentPickerOpen: false,
|
||||||
editMode: false,
|
editMode: false,
|
||||||
titleFocused: false,
|
titleFocused: false,
|
||||||
|
@ -117,7 +121,7 @@ class PublishPage extends React.PureComponent {
|
||||||
|
|
||||||
// input data
|
// input data
|
||||||
hasEditedContentAddress: false,
|
hasEditedContentAddress: false,
|
||||||
bid: 0.1,
|
bid: 0.01,
|
||||||
description: null,
|
description: null,
|
||||||
title: null,
|
title: null,
|
||||||
language: 'en',
|
language: 'en',
|
||||||
|
@ -137,6 +141,9 @@ class PublishPage extends React.PureComponent {
|
||||||
|
|
||||||
// other
|
// other
|
||||||
publishStarted: false,
|
publishStarted: false,
|
||||||
|
storagePermissionRequired: false,
|
||||||
|
hasReturnedBack: false,
|
||||||
|
returnUrl: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
didFocusListener;
|
didFocusListener;
|
||||||
|
@ -158,6 +165,8 @@ class PublishPage extends React.PureComponent {
|
||||||
DeviceEventEmitter.removeListener('onAllGalleryThumbnailsChecked', this.handleAllGalleryThumbnailsChecked);
|
DeviceEventEmitter.removeListener('onAllGalleryThumbnailsChecked', this.handleAllGalleryThumbnailsChecked);
|
||||||
DeviceEventEmitter.removeListener('onDocumentPickerFilePicked', this.onFilePicked);
|
DeviceEventEmitter.removeListener('onDocumentPickerFilePicked', this.onFilePicked);
|
||||||
DeviceEventEmitter.removeListener('onDocumentPickerCanceled', this.onPickerCanceled);
|
DeviceEventEmitter.removeListener('onDocumentPickerCanceled', this.onPickerCanceled);
|
||||||
|
DeviceEventEmitter.removeListener('onStoragePermissionGranted', this.handleStoragePermissionGranted);
|
||||||
|
DeviceEventEmitter.removeListener('onStoragePermissionRefused', this.handleStoragePermissionRefused);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleGalleryThumbnailChecked = evt => {
|
handleGalleryThumbnailChecked = evt => {
|
||||||
|
@ -180,28 +189,57 @@ class PublishPage extends React.PureComponent {
|
||||||
this.setState({ ...publishFormState, advancedMode });
|
this.setState({ ...publishFormState, advancedMode });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checkStoragePermission = () => {
|
||||||
|
// check if we the permission to write to external storage has been granted
|
||||||
|
NativeModules.UtilityModule.canReadWriteStorage().then(canReadWrite => {
|
||||||
|
if (!canReadWrite) {
|
||||||
|
// request permission
|
||||||
|
NativeModules.UtilityModule.requestStoragePermission();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadVideos = () => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
loadingVideos: true,
|
||||||
|
storagePermissionRequired: false,
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
NativeModules.Gallery.getVideos().then(videos => this.setState({ videos, loadingVideos: false }));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
onComponentFocused = () => {
|
onComponentFocused = () => {
|
||||||
const { balance, hasFormState, pushDrawerStack, setPlayerVisible, navigation } = this.props;
|
const { balance, fetchMyClaims, hasFormState, pushDrawerStack, setPlayerVisible, navigation } = this.props;
|
||||||
NativeModules.Firebase.setCurrentScreen('Publish').then(result => {
|
NativeModules.Firebase.setCurrentScreen('Publish').then(result => {
|
||||||
pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH, navigation.state.params ? navigation.state.params : null);
|
pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH, navigation.state.params ? navigation.state.params : null);
|
||||||
setPlayerVisible();
|
setPlayerVisible();
|
||||||
|
|
||||||
|
DeviceEventEmitter.addListener('onStoragePermissionGranted', this.handleStoragePermissionGranted);
|
||||||
|
DeviceEventEmitter.addListener('onStoragePermissionRefused', this.handleStoragePermissionRefused);
|
||||||
|
|
||||||
NativeModules.Gallery.canUseCamera().then(canUseCamera => this.setState({ canUseCamera }));
|
NativeModules.Gallery.canUseCamera().then(canUseCamera => this.setState({ canUseCamera }));
|
||||||
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => this.setState({ thumbnailPath }));
|
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => this.setState({ thumbnailPath }));
|
||||||
this.setState(
|
fetchMyClaims();
|
||||||
{
|
|
||||||
loadingVideos: true,
|
NativeModules.UtilityModule.canReadWriteStorage().then(canReadWrite => {
|
||||||
},
|
if (!canReadWrite) {
|
||||||
() => {
|
this.setState({ storagePermissionRequired: true }, () =>
|
||||||
NativeModules.Gallery.getVideos().then(videos => this.setState({ videos, loadingVideos: false }));
|
NativeModules.UtilityModule.requestStoragePermission(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Can only load videos when the storage permission is granted
|
||||||
|
this.loadVideos();
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
// Check if this is an edit action
|
// Check if this is an edit action
|
||||||
let isEditMode = false,
|
let isEditMode = false,
|
||||||
vanityUrlSet = false;
|
vanityUrlSet = false;
|
||||||
if (navigation.state.params) {
|
if (navigation.state.params) {
|
||||||
const { displayForm, editMode, claimToEdit, vanityUrl } = navigation.state.params;
|
const { displayForm, editMode, claimToEdit, vanityUrl, returnUrl } = navigation.state.params;
|
||||||
if (editMode) {
|
if (editMode) {
|
||||||
this.prepareEdit(claimToEdit);
|
this.prepareEdit(claimToEdit);
|
||||||
isEditMode = true;
|
isEditMode = true;
|
||||||
|
@ -215,6 +253,7 @@ class PublishPage extends React.PureComponent {
|
||||||
vanityUrl: claimName,
|
vanityUrl: claimName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.setState({ returnUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEditMode && hasFormState) {
|
if (!isEditMode && hasFormState) {
|
||||||
|
@ -286,7 +325,7 @@ class PublishPage extends React.PureComponent {
|
||||||
this.handleChannelChange(channelName);
|
this.handleChannelChange(channelName);
|
||||||
}
|
}
|
||||||
pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH_FORM);
|
pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH_FORM);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -322,7 +361,7 @@ class PublishPage extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePublishPressed = () => {
|
handlePublishPressed = () => {
|
||||||
const { balance, notify, publish, updatePublishForm } = this.props;
|
const { balance, myClaims, notify, publish, updatePublishForm } = this.props;
|
||||||
const {
|
const {
|
||||||
editMode,
|
editMode,
|
||||||
bid,
|
bid,
|
||||||
|
@ -354,18 +393,34 @@ class PublishPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!title || title.trim().length === 0) {
|
if (!title || title.trim().length === 0) {
|
||||||
notify({ message: __('Please provide a title') });
|
notify({ message: __('Please provide a title'), isError: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
notify({ message: __('Please specify an address where people can find your content.') });
|
notify({ message: __('Please specify an address where people can find your content.'), isError: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isNameValid(name, false)) {
|
||||||
|
notify({ message: __('Your content address contains invalid characters.'), isError: true });
|
||||||
|
return;
|
||||||
|
} else if (!editMode && myClaims && myClaims.length > 0) {
|
||||||
|
if (myClaims.some(claim => claim.name.toLowerCase() === name.trim().toLowerCase())) {
|
||||||
|
notify({
|
||||||
|
message: __('You have already published to the specified content address. Please enter a new address.'),
|
||||||
|
isError: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!currentMedia && !editMode) {
|
if (!currentMedia && !editMode) {
|
||||||
// sanity check. normally shouldn't happen
|
// sanity check. normally shouldn't happen
|
||||||
notify({ message: __('No file selected. Please select a video or take a photo before publishing.') });
|
notify({
|
||||||
|
message: __('No file selected. Please select a video or take a photo before publishing.'),
|
||||||
|
isError: true,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +476,15 @@ class PublishPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const { currentRoute: prevRoute, drawerStack: prevDrawerStack, notify, updatePublishFormState } = this.props;
|
const {
|
||||||
|
currentRoute: prevRoute,
|
||||||
|
drawerStack: prevDrawerStack,
|
||||||
|
popDrawerStack,
|
||||||
|
setPlayerVisible,
|
||||||
|
navigation,
|
||||||
|
notify,
|
||||||
|
updatePublishFormState,
|
||||||
|
} = this.props;
|
||||||
const { currentRoute, drawerStack, publishFormValues } = nextProps;
|
const { currentRoute, drawerStack, publishFormValues } = nextProps;
|
||||||
|
|
||||||
if (Constants.DRAWER_ROUTE_PUBLISH === currentRoute && currentRoute !== prevRoute) {
|
if (Constants.DRAWER_ROUTE_PUBLISH === currentRoute && currentRoute !== prevRoute) {
|
||||||
|
@ -435,6 +498,11 @@ class PublishPage extends React.PureComponent {
|
||||||
) {
|
) {
|
||||||
// navigated back from the form
|
// navigated back from the form
|
||||||
this.showSelector();
|
this.showSelector();
|
||||||
|
if (!this.state.hasReturnedBack && this.state.returnUrl) {
|
||||||
|
this.setState({ hasReturnedBack: true }, () => {
|
||||||
|
navigateBack(navigation, drawerStack, popDrawerStack, setPlayerVisible);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +522,7 @@ class PublishPage extends React.PureComponent {
|
||||||
() => {
|
() => {
|
||||||
this.handleNameChange(this.state.name);
|
this.handleNameChange(this.state.name);
|
||||||
pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH_FORM);
|
pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH_FORM);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -508,7 +576,7 @@ class PublishPage extends React.PureComponent {
|
||||||
clearPublishFormState();
|
clearPublishFormState();
|
||||||
// reset thumbnail
|
// reset thumbnail
|
||||||
updatePublishForm({ thumbnail: null });
|
updatePublishForm({ thumbnail: null });
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,7 +603,7 @@ class PublishPage extends React.PureComponent {
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
NativeModules.UtilityModule.openDocumentPicker('*/*');
|
NativeModules.UtilityModule.openDocumentPicker('*/*');
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -581,12 +649,12 @@ class PublishPage extends React.PureComponent {
|
||||||
() => {
|
() => {
|
||||||
// upload a new thumbnail
|
// upload a new thumbnail
|
||||||
uploadImageAsset(fileUrl, this.handleThumbnailUploadSuccess, this.handleThumbnailUploadFailure);
|
uploadImageAsset(fileUrl, this.handleThumbnailUploadSuccess, this.handleThumbnailUploadFailure);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// could not determine the file path
|
// could not determine the file path
|
||||||
notify({ message: __('The path could not be determined. Please try a different file.') });
|
notify({ message: __('The path could not be determined. Please try a different file.'), isError: true });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -632,7 +700,7 @@ class PublishPage extends React.PureComponent {
|
||||||
videoRecordingMode: false,
|
videoRecordingMode: false,
|
||||||
recordingVideo: false,
|
recordingVideo: false,
|
||||||
},
|
},
|
||||||
() => pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH_FORM)
|
() => pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH_FORM),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -656,7 +724,7 @@ class PublishPage extends React.PureComponent {
|
||||||
showCameraOverlay: false,
|
showCameraOverlay: false,
|
||||||
videoRecordingMode: false,
|
videoRecordingMode: false,
|
||||||
},
|
},
|
||||||
() => pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH)
|
() => pushDrawerStack(Constants.DRAWER_ROUTE_PUBLISH),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -775,8 +843,8 @@ class PublishPage extends React.PureComponent {
|
||||||
uploadImageAsset(
|
uploadImageAsset(
|
||||||
this.getFilePathFromUri(uri),
|
this.getFilePathFromUri(uri),
|
||||||
this.handleThumbnailUploadSuccess,
|
this.handleThumbnailUploadSuccess,
|
||||||
this.handleThumbnailUploadFailure
|
this.handleThumbnailUploadFailure,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (mediaType === 'image' || mediaType === 'video') {
|
} else if (mediaType === 'image' || mediaType === 'video') {
|
||||||
|
@ -789,7 +857,7 @@ class PublishPage extends React.PureComponent {
|
||||||
this.setState({ currentThumbnailUri: `file://${path}`, updatingThumbnailUri: false });
|
this.setState({ currentThumbnailUri: `file://${path}`, updatingThumbnailUri: false });
|
||||||
if (!this.state.uploadedThumbnailUri) {
|
if (!this.state.uploadedThumbnailUri) {
|
||||||
this.setState({ uploadThumbnailStarted: true }, () =>
|
this.setState({ uploadThumbnailStarted: true }, () =>
|
||||||
uploadImageAsset(path, this.handleThumbnailUploadSuccess, this.handleThumbnailUploadFailure)
|
uploadImageAsset(path, this.handleThumbnailUploadSuccess, this.handleThumbnailUploadFailure),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -815,7 +883,7 @@ class PublishPage extends React.PureComponent {
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this.handleNameChange(this.state.name);
|
this.handleNameChange(this.state.name);
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -879,12 +947,35 @@ class PublishPage extends React.PureComponent {
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
NativeModules.UtilityModule.openDocumentPicker('image/*');
|
NativeModules.UtilityModule.openDocumentPicker('image/*');
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleStoragePermissionGranted = () => {
|
||||||
|
// update the configured download folder
|
||||||
|
this.loadVideos();
|
||||||
|
NativeModules.UtilityModule.getDownloadDirectory().then(downloadDirectory => {
|
||||||
|
Lbry.settings_set({
|
||||||
|
key: 'download_dir',
|
||||||
|
value: downloadDirectory,
|
||||||
|
})
|
||||||
|
.then(() => {})
|
||||||
|
.catch(() => {});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleStoragePermissionRefused = () => {
|
||||||
|
const { notify } = this.props;
|
||||||
|
notify({
|
||||||
|
message: __(
|
||||||
|
'Content from your device cannot be published because the permission to read storage was not granted.',
|
||||||
|
),
|
||||||
|
isError: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { balance, navigation, notify, publishFormValues } = this.props;
|
const { balance, navigation, notify, sdkReady } = this.props;
|
||||||
const {
|
const {
|
||||||
allThumbnailsChecked,
|
allThumbnailsChecked,
|
||||||
canUseCamera,
|
canUseCamera,
|
||||||
|
@ -892,10 +983,24 @@ class PublishPage extends React.PureComponent {
|
||||||
currentPhase,
|
currentPhase,
|
||||||
checkedThumbnails,
|
checkedThumbnails,
|
||||||
loadingVideos,
|
loadingVideos,
|
||||||
|
storagePermissionRequired,
|
||||||
thumbnailPath,
|
thumbnailPath,
|
||||||
videos,
|
videos,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
if (!sdkReady) {
|
||||||
|
return (
|
||||||
|
<View style={publishStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
<EmptyStateView
|
||||||
|
message={__(
|
||||||
|
'The background service is still initializing. You can still explore and watch content during the initialization process.',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (Constants.PHASE_SELECTOR === currentPhase) {
|
if (Constants.PHASE_SELECTOR === currentPhase) {
|
||||||
content = (
|
content = (
|
||||||
|
@ -933,13 +1038,20 @@ class PublishPage extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{(loadingVideos || !allThumbnailsChecked) && (
|
{storagePermissionRequired && (
|
||||||
|
<View style={publishStyle.relativeCentered}>
|
||||||
|
<Text style={publishStyle.noVideos}>
|
||||||
|
{__('The storage permission is required to be able to display and publish your videos.')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{!storagePermissionRequired && (loadingVideos || !allThumbnailsChecked) && (
|
||||||
<View style={publishStyle.loadingView}>
|
<View style={publishStyle.loadingView}>
|
||||||
<ActivityIndicator size="small" color={Colors.NextLbryGreen} />
|
<ActivityIndicator size="small" color={Colors.NextLbryGreen} />
|
||||||
<Text style={publishStyle.loadingText}>{__('Please wait while we load your videos...')}</Text>
|
<Text style={publishStyle.loadingText}>{__('Please wait while we load your videos...')}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{!loadingVideos && (!videos || videos.length === 0) && (
|
{!storagePermissionRequired && !loadingVideos && (!videos || videos.length === 0) && (
|
||||||
<View style={publishStyle.relativeCentered}>
|
<View style={publishStyle.relativeCentered}>
|
||||||
<Text style={publishStyle.noVideos}>
|
<Text style={publishStyle.noVideos}>
|
||||||
{__('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.')}
|
||||||
|
@ -1110,7 +1222,7 @@ class PublishPage extends React.PureComponent {
|
||||||
{__('The address where people can find your content (ex. lbry://myvideo). ')}
|
{__('The address where people can find your content (ex. lbry://myvideo). ')}
|
||||||
{this.state.editMode &&
|
{this.state.editMode &&
|
||||||
__(
|
__(
|
||||||
'You cannot change this address while editing your content. If you wish to use a new address, please republish the content.'
|
'You cannot change this address while editing your content. If you wish to use a new address, please republish the content.',
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
@ -1133,8 +1245,16 @@ class PublishPage extends React.PureComponent {
|
||||||
keyboardType={'numeric'}
|
keyboardType={'numeric'}
|
||||||
value={String(this.state.bid)}
|
value={String(this.state.bid)}
|
||||||
onChangeText={this.handleBidChange}
|
onChangeText={this.handleBidChange}
|
||||||
|
onFocus={() => this.setState({ creditsInputFocused: true })}
|
||||||
|
onBlur={() => this.setState({ creditsInputFocused: false })}
|
||||||
/>
|
/>
|
||||||
<Text style={publishStyle.currency}>LBC</Text>
|
<Text style={publishStyle.currency}>LBC</Text>
|
||||||
|
<View style={publishStyle.balance}>
|
||||||
|
{this.state.creditsInputFocused && <Icon name="coins" size={12} />}
|
||||||
|
{this.state.creditsInputFocused && (
|
||||||
|
<Text style={publishStyle.balanceText}>{formatCredits(parseFloat(balance), 1, true)}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Text style={publishStyle.helpText}>
|
<Text style={publishStyle.helpText}>
|
||||||
{__('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.')}
|
||||||
|
@ -1252,7 +1372,7 @@ class PublishPage extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
<Text style={publishStyle.successText}>
|
<Text style={publishStyle.successText}>
|
||||||
{__(
|
{__(
|
||||||
'Your content will be live in a few minutes. In the mean time, feel free to publish more content or explore the app.'
|
'Your content will be live in a few minutes. In the mean time, feel free to publish more content or explore the app.',
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
selectIsFetchingClaimListMine,
|
selectIsFetchingClaimListMine,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import PublishesPage from './view';
|
import PublishesPage from './view';
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ const select = state => ({
|
||||||
uris: selectMyClaimUrisWithoutChannels(state),
|
uris: selectMyClaimUrisWithoutChannels(state),
|
||||||
fetching: selectIsFetchingClaimListMine(state),
|
fetching: selectIsFetchingClaimListMine(state),
|
||||||
pendingClaims: selectPendingClaims(state),
|
pendingClaims: selectPendingClaims(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
@ -27,7 +29,4 @@ const perform = dispatch => ({
|
||||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(PublishesPage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(PublishesPage);
|
|
||||||
|
|
|
@ -114,14 +114,27 @@ class PublishesPage extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{ cancelable: true }
|
{ cancelable: true },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fetching, navigation, uris } = this.props;
|
const { fetching, navigation, sdkReady, uris } = this.props;
|
||||||
const { selectionMode, selectedUris } = this.state;
|
const { selectionMode, selectedUris } = this.state;
|
||||||
|
|
||||||
|
if (!sdkReady) {
|
||||||
|
return (
|
||||||
|
<View style={publishStyle.container}>
|
||||||
|
<UriBar navigation={navigation} />
|
||||||
|
<EmptyStateView
|
||||||
|
message={__(
|
||||||
|
'The background service is still initializing. You can still explore and watch content during the initialization process.',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={publishStyle.container}>
|
<View style={publishStyle.container}>
|
||||||
<UriBar
|
<UriBar
|
||||||
|
@ -181,7 +194,7 @@ class PublishesPage extends React.PureComponent {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// TODO: when shortUrl is available for my claims, navigate to that URL instead
|
// TODO: when shortUrl is available for my claims, navigate to that URL instead
|
||||||
navigateToUri(navigation, item);
|
navigateToUri(navigation, claim.permanent_url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -12,7 +12,8 @@ import {
|
||||||
import { doToast } from 'lbry-redux';
|
import { doToast } from 'lbry-redux';
|
||||||
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
|
||||||
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
import { selectCurrentRoute } from 'redux/selectors/drawer';
|
||||||
import Constants from 'constants';
|
import { selectSdkReady } from 'redux/selectors/settings';
|
||||||
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import RewardsPage from './view';
|
import RewardsPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -22,6 +23,7 @@ const select = state => ({
|
||||||
emailVerifyPending: selectEmailVerifyIsPending(state),
|
emailVerifyPending: selectEmailVerifyIsPending(state),
|
||||||
fetching: selectFetchingRewards(state),
|
fetching: selectFetchingRewards(state),
|
||||||
rewards: selectUnclaimedRewards(state),
|
rewards: selectUnclaimedRewards(state),
|
||||||
|
sdkReady: selectSdkReady(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -33,7 +35,4 @@ const perform = dispatch => ({
|
||||||
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(RewardsPage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(RewardsPage);
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue