Compare commits

..

5 commits

Author SHA1 Message Date
Akinwale Ariwodola
6e4765a743 fixes from ui/ux review 2019-08-25 17:07:50 +01:00
Akinwale Ariwodola
6217c45322 fix merge conflict and buildURI 2019-08-25 16:41:20 +01:00
Akinwale Ariwodola
dfb43199ea fix: error from clicking post-publish link 2019-08-22 16:31:13 +01:00
Akinwale Ariwodola
42488c0b39 remove console.log 2019-08-22 15:55:09 +01:00
Akinwale Ariwodola
330997bc1e edit published content 2019-08-22 15:51:57 +01:00
214 changed files with 13312 additions and 20453 deletions

View file

@ -10,7 +10,6 @@
"__": 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
View file

@ -1,66 +1,4 @@
# 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

View file

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

3
.gitmodules vendored
View file

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

View file

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2017-2020 LBRY Inc Copyright (c) 2017-2018 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:

View file

@ -1,7 +1,7 @@
# LBRY React Native # LBRY Android
[![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)
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. 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.
<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,41 +12,10 @@ 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** 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 Browser** 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
### Software Requirements 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.
* 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

View file

@ -1,14 +0,0 @@
/**
* @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 +0,0 @@
Subproject commit ff30e7f6a4358fd997a9e6d9f75bfe6959eafcb6

View file

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

View file

@ -1,7 +1,3 @@
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;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,129 +0,0 @@
<?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>

View file

@ -1,15 +0,0 @@
/**
* 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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,57 +0,0 @@
<?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>

View file

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

View file

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

View file

@ -1,72 +0,0 @@
/**
* 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

View file

@ -1,53 +0,0 @@
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

View file

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

10433
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,90 +1,72 @@
{ {
"name": "LBRYApp", "name": "LBRYApp",
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": "true",
"scripts": { "scripts": {
"android": "react-native run-android", "start": "node node_modules/react-native/local-cli/cli.js start",
"ios": "react-native run-ios", "devtools": "react-devtools",
"start": "react-native start", "format": "prettier 'src/**/*.{js,json}' --write",
"test": "jest", "precommit": "lint-staged"
"lint": "eslint .", },
"format": "prettier 'src/**/*.{js,json}' --write", "dependencies": {
"precommit": "lint-staged" "base-64": "^0.1.0",
}, "@expo/vector-icons": "^8.1.0",
"dependencies": { "gfycat-style-urls": "^1.0.3",
"base-64": "^0.1.0", "lbry-redux": "lbryio/lbry-redux#73f10d488d5fd5df7aa806b60c8df5c948ca3c9a",
"@expo/vector-icons": "^8.1.0", "lbryinc": "lbryio/lbryinc#17868d948a160af27a375956226f8dd23fa2c37d",
"gfycat-style-urls": "^1.0.3", "lodash": ">=4.17.11",
"lbry-redux": "lbryio/lbry-redux#69ffd110dbf3633e5847f61f008751edec033017", "merge": ">=1.2.1",
"lbryinc": "lbryio/lbryinc#667024ebb7cb207609273174ca422cee47469270", "moment": "^2.22.1",
"lodash": ">=4.17.11", "react": "16.8.6",
"merge": ">=1.2.1", "react-native": "0.59.10",
"moment": "^2.22.1", "@react-native-community/async-storage": "^1.5.1",
"react": "16.9.0", "react-native-camera": "^2.11.1",
"react-native": "0.61.5", "react-native-country-picker-modal": "^0.6.2",
"@react-native-community/async-storage": "^1.5.1", "react-native-document-picker": "^3.2.4",
"@react-native-community/masked-view": "^0.1.5", "react-native-exception-handler": "2.9.0",
"react-native-camera": "^3.15.0", "react-native-fast-image": "^6.1.1",
"react-native-country-picker-modal": "^1.10.0", "react-native-fs": "^2.13.3",
"react-native-exception-handler": "2.10.8", "react-native-gesture-handler": "^1.1.0",
"react-native-fast-image": "^7.0.2", "react-native-image-zoom-viewer": "^2.2.5",
"react-native-fs": "^2.16.6", "react-native-password-strength-meter": "^0.0.2",
"react-native-gesture-handler": "1.5.2", "react-native-phone-input": "lbryio/react-native-phone-input",
"react-native-image-zoom-viewer": "^2.2.5", "react-native-reanimated": "^1.2.0",
"react-native-password-strength-meter": "^0.0.2", "react-native-super-grid": "^3.0.4",
"react-native-phone-input": "lbryio/react-native-phone-input", "react-native-vector-icons": "^6.6.0",
"react-native-progress-circle": "2.1.0", "react-native-video": "lbryio/react-native-video#exoplayer-lbry-android",
"react-native-reanimated": "1.4.0", "react-navigation": "^3.11.0",
"react-native-safe-area-context": "^0.6.2", "react-navigation-drawer": "^2.0.0",
"react-native-screens": "^2.0.0", "react-navigation-redux-helpers": "^3.0.2",
"react-native-snackbar": "2.0.4", "react-redux": "^5.0.3",
"react-native-super-grid": "^3.0.4", "redux": "^4.0.4",
"react-native-vector-icons": "^6.6.0", "redux-persist": "^5.10.0",
"react-native-video": "lbryio/react-native-video#7992ff945872f9bd00a3736d9ff1318f343abf47", "redux-persist-filesystem-storage": "^1.3.2",
"react-native-webview": "^8.0.2", "redux-persist-transform-compress": "^4.2.0",
"react-navigation": "^4.0.10", "redux-persist-transform-filter": "0.0.18",
"react-navigation-drawer": "2.3.3", "redux-thunk": "^2.3.0",
"react-navigation-redux-helpers": "^3.0.2", "rn-fetch-blob": "0.10.15"
"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"
}
} }

View file

@ -1,12 +0,0 @@
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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

View file

@ -1,13 +1,10 @@
import React from 'react'; import React from 'react';
import AboutPage from 'page/about'; import AboutPage from 'page/about';
import ChannelCreatorPage from 'page/channelCreator';
import DiscoverPage from 'page/discover'; 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';
@ -20,44 +17,18 @@ 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, StackActions } from 'react-navigation'; import { createStackNavigator, NavigationActions } from 'react-navigation';
import { createDrawerNavigator } from 'react-navigation-drawer'; import { createDrawerNavigator } from 'react-navigation-drawer';
import { createStackNavigator } from 'react-navigation-stack';
import { import {
createReduxContainer, createReduxContainer,
createReactNavigationReduxMiddleware, createReactNavigationReduxMiddleware,
createNavigationReducer, createNavigationReducer,
} from 'react-navigation-redux-helpers'; } from 'react-navigation-redux-helpers';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { AppState, BackHandler, Linking, NativeModules, TextInput, ToastAndroid } from 'react-native';
AppState,
Alert,
BackHandler,
DeviceEventEmitter,
Linking,
NativeModules,
StatusBar,
TextInput,
ToastAndroid,
} from 'react-native';
import { selectDrawerStack } from 'redux/selectors/drawer'; import { selectDrawerStack } from 'redux/selectors/drawer';
import { SETTINGS, doDismissToast, doToast, selectToast } from 'lbry-redux';
import { import {
Lbry,
ACTIONS,
SETTINGS,
doBalanceSubscribe,
doDismissToast,
doPopulateSharedUserState,
doPreferenceGet,
doToast,
selectToast,
} from 'lbry-redux';
import {
Lbryio,
rewards as REWARD_TYPES,
doBlackListedOutpointsSubscribe,
doClaimRewardType,
doFilteredOutpointsSubscribe,
doGetSync, doGetSync,
doUserCheckEmailVerified, doUserCheckEmailVerified,
doUserEmailVerify, doUserEmailVerify,
@ -65,13 +36,11 @@ import {
selectEmailToVerify, selectEmailToVerify,
selectEmailVerifyIsPending, selectEmailVerifyIsPending,
selectEmailVerifyErrorMessage, selectEmailVerifyErrorMessage,
selectHashChanged,
selectUser, selectUser,
} from 'lbryinc'; } from 'lbryinc';
import { doStartDownload, doUpdateDownload, doCompleteDownload } from 'redux/actions/file'; import { makeSelectClientSetting } 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 } from 'utils/helper';
import AsyncStorage from '@react-native-community/async-storage'; import AsyncStorage from '@react-native-community/async-storage';
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
@ -80,20 +49,25 @@ 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 menuNavigationButton = navigation => (
<NavigationButton
name="bars"
size={24}
style={discoverStyle.drawerMenuButton}
iconStyle={discoverStyle.drawerHamburger}
onPress={() => navigation.openDrawer()}
/>
);
const discoverStack = createStackNavigator( const discoverStack = createStackNavigator(
{ {
Subscriptions: { Discover: {
screen: SubscriptionsPage, screen: DiscoverPage,
navigationOptions: { navigationOptions: ({ navigation }) => ({
title: 'Following', title: 'Explore',
header: null, header: null,
drawerIcon: ({ tintColor }) => <Icon name="heart" solid size={drawerIconSize} style={{ color: tintColor }} />, }),
},
}, },
File: { File: {
screen: FilePage, screen: FilePage,
@ -117,7 +91,7 @@ const discoverStack = createStackNavigator(
{ {
headerMode: 'screen', headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }), transitionConfig: () => ({ screenInterpolator: () => null }),
}, }
); );
discoverStack.navigationOptions = ({ navigation }) => { discoverStack.navigationOptions = ({ navigation }) => {
@ -151,7 +125,7 @@ const walletStack = createStackNavigator(
{ {
headerMode: 'screen', headerMode: 'screen',
transitionConfig: () => ({ screenInterpolator: () => null }), transitionConfig: () => ({ screenInterpolator: () => null }),
}, }
); );
const drawerIconSize = 18; const drawerIconSize = 18;
@ -160,17 +134,10 @@ const drawer = createDrawerNavigator(
DiscoverStack: { DiscoverStack: {
screen: discoverStack, screen: discoverStack,
navigationOptions: { navigationOptions: {
title: 'Following', title: 'Explore',
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: {
@ -178,6 +145,13 @@ 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: {
@ -185,12 +159,6 @@ const drawer = createDrawerNavigator(
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={drawerIconSize} style={{ color: tintColor }} />, drawerIcon: ({ tintColor }) => <Icon name="wallet" size={drawerIconSize} style={{ color: tintColor }} />,
}, },
}, },
ChannelCreator: {
screen: ChannelCreatorPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="at" size={drawerIconSize} style={{ color: tintColor }} />,
},
},
Publish: { Publish: {
screen: PublishPage, screen: PublishPage,
navigationOptions: { navigationOptions: {
@ -211,12 +179,6 @@ 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: {
@ -240,18 +202,16 @@ const drawer = createDrawerNavigator(
}, },
}, },
{ {
drawerWidth: 299, drawerWidth: 300,
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(
@ -277,16 +237,10 @@ 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;
@ -305,11 +259,9 @@ class AppWithNavigationState extends React.Component {
constructor() { constructor() {
super(); super();
this.emailVerifyCheckInterval = null; this.emailVerifyCheckInterval = null;
this.syncGetInterval = null;
this.state = { this.state = {
emailVerifyDone: false, emailVerifyDone: false,
verifyPending: false, verifyPending: false,
syncHashChanged: false,
}; };
} }
@ -325,30 +277,13 @@ class AppWithNavigationState extends React.Component {
} }
return false; return false;
}.bind(this), }.bind(this)
); );
} }
componentDidMount() { componentDidMount() {
const { dispatch, user } = this.props;
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
this.syncGetInterval = setInterval(() => {
this.setState({ syncHashChanged: false }); // reset local state
if (user && user.has_verified_email) {
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(walletPassword => {
dispatch(doGetSync(walletPassword, () => this.getUserSettings()));
});
}
}, SYNC_GET_INTERVAL);
} }
checkEmailVerification = () => { checkEmailVerification = () => {
@ -361,152 +296,28 @@ class AppWithNavigationState extends React.Component {
}); });
}; };
getUserSettings = () => {
const { dispatch } = this.props;
doPreferenceGet(
'shared',
preference => {
dispatch(doPopulateSharedUserState(preference));
},
error => {
/* failed */
},
);
};
checkNewAndroidReward = () => {
const { dispatch, doToast } = this.props;
const claimRewardCallback = err => {
if (err) {
// an error occurred, do not display anything
return;
}
// reward successfully claimed
NativeModules.UtilityModule.setNativeBooleanSetting(Constants.SETTING_NEW_ANDROID_REWARD_CLAIMED, true);
};
NativeModules.UtilityModule.getNativeBooleanSetting(Constants.SETTING_NEW_ANDROID_REWARD_CLAIMED, false).then(
rewardClaimed => {
if (!rewardClaimed) {
dispatch(
doClaimRewardType(REWARD_TYPES.TYPE_NEW_ANDROID, { notifyError: false, callback: claimRewardCallback }),
);
}
},
);
};
handleSdkReady = () => {
const { dispatch } = this.props;
dispatch(doSetSdkReady());
dispatch(doBalanceSubscribe());
dispatch(doBlackListedOutpointsSubscribe());
dispatch(doFilteredOutpointsSubscribe());
Lbry.wallet_status().then(secureWalletStatus => {
// For now, automatically unlock the wallet if a password is set so that downloads work
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password => {
if ((secureWalletStatus.is_encrypted && !secureWalletStatus.is_locked) || secureWalletStatus.is_locked) {
this.setState({
message: __('Unlocking account'),
details: __('Decrypting wallet'),
});
// unlock the wallet and then finish the splash screen
Lbry.wallet_unlock({ password: password || '' }).then(unlocked => {
if (unlocked) {
} else {
// this.handleAccountUnlockFailed();
}
});
}
});
});
this.checkNewAndroidReward();
};
handleAccountUnlockFailed() {
const { dispatch } = this.props;
dispatch(
doToast({
message: __(
'Your wallet failed to unlock, which means you may not be able to play any videos or access your funds. Restart the app to fix this.',
),
isError: true,
}),
);
}
handleDownloadStarted = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo } = evt;
dispatch(doStartDownload(uri, outpoint, fileInfo));
};
handleDownloadUpdated = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo, progress } = evt;
dispatch(doUpdateDownload(uri, outpoint, fileInfo, progress));
};
handleDownloadCompleted = evt => {
const { dispatch } = this.props;
const { uri, outpoint, fileInfo } = evt;
dispatch(doCompleteDownload(uri, outpoint, fileInfo));
};
handleDownloadAborted = evt => {
const { dispatch } = this.props;
const { uri, outpoint } = evt;
dispatch({
type: ACTIONS.DOWNLOADING_CANCELED,
data: { uri, outpoint },
});
dispatch({
type: ACTIONS.FILE_DELETE,
data: {
outpoint,
},
});
};
componentWillUnmount() { 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);
if (this.emailVerifyCheckInterval > -1) {
clearInterval(this.emailVerifyCheckInterval);
}
if (this.syncGetInterval > -1) {
clearInterval(this.syncGetInterval);
}
} }
componentDidUpdate() { componentDidUpdate() {
const { dispatch, user, hashChanged } = this.props; const { dispatch, user } = this.props;
if (this.state.verifyPending && this.emailVerifyCheckInterval > 0 && user && user.has_verified_email) { if (this.state.verifyPending && this.emailVerifyCheckInterval > 0 && user && user.has_verified_email) {
clearInterval(this.emailVerifyCheckInterval); clearInterval(this.emailVerifyCheckInterval);
AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'false'); AsyncStorage.setItem(Constants.KEY_EMAIL_VERIFY_PENDING, 'false');
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 });
Snackbar.show({ title: __('Your email address was successfully verified.'), duration: Snackbar.LENGTH_LONG }); ToastAndroid.show('Your email address was successfully verified.', ToastAndroid.LONG);
// get user settings after email verification // upon successful email verification, do wallet sync (if password has been set)
this.getUserSettings(); NativeModules.UtilityModule.getSecureValue(Constants.KEY_FIRST_RUN_PASSWORD).then(walletPassword => {
} if (walletPassword && walletPassword.trim().length > 0) {
dispatch(doGetSync(walletPassword));
if (hashChanged && !this.state.syncHashChanged) { }
this.setState({ syncHashChanged: true }); });
this.getUserSettings();
} }
} }
@ -515,7 +326,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, isError } = toast; const { message } = 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
@ -523,14 +334,7 @@ class AppWithNavigationState extends React.Component {
} }
if (currentDisplayType === 'toast') { if (currentDisplayType === 'toast') {
const props = { ToastAndroid.show(message, ToastAndroid.LONG);
title: message,
duration: Snackbar.LENGTH_LONG,
};
if (isError) {
props.backgroundColor = Colors.Red;
}
Snackbar.show(props);
} }
dispatch(doDismissToast()); dispatch(doDismissToast());
@ -542,7 +346,7 @@ class AppWithNavigationState extends React.Component {
this.setState({ emailVerifyDone: true }); this.setState({ emailVerifyDone: true });
const message = emailVerifyErrorMessage const message = emailVerifyErrorMessage
? String(emailVerifyErrorMessage) ? String(emailVerifyErrorMessage)
: __('Your email address was successfully verified.'); : 'Your email address was successfully verified.';
if (!emailVerifyErrorMessage) { if (!emailVerifyErrorMessage) {
AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL); AsyncStorage.removeItem(Constants.KEY_FIRST_RUN_EMAIL);
} }
@ -577,26 +381,6 @@ class AppWithNavigationState extends React.Component {
if (backgroundPlayEnabled || NativeModules.BackgroundMedia) { if (backgroundPlayEnabled || NativeModules.BackgroundMedia) {
NativeModules.BackgroundMedia.hidePlaybackNotification(); NativeModules.BackgroundMedia.hidePlaybackNotification();
} }
// check fullscreen mode and show / hide navigation bar accordingly
this.checkFullscreenMode();
}
};
checkFullscreenMode = () => {
const { fullscreenMode } = this.props;
StatusBar.setHidden(fullscreenMode);
if (fullscreenMode) {
// fullscreen, so change orientation to landscape mode
NativeModules.ScreenOrientation.lockOrientationLandscape();
// hide the navigation bar (on devices that have the soft navigation bar)
NativeModules.UtilityModule.hideNavigationBar();
} else {
// Switch back to portrait mode when the media is not fullscreen
NativeModules.ScreenOrientation.lockOrientationPortrait();
// hide the navigation bar (on devices that have the soft navigation bar)
NativeModules.UtilityModule.showNavigationBar();
} }
}; };
@ -609,7 +393,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) {
@ -617,7 +401,7 @@ class AppWithNavigationState extends React.Component {
try { try {
dispatch(doUserEmailVerify(verification.token, verification.recaptcha)); dispatch(doUserEmailVerify(verification.token, verification.recaptcha));
} catch (error) { } catch (error) {
const message = __('Invalid Verification Token'); const message = 'Invalid Verification Token';
dispatch(doUserEmailVerifyFailure(message)); dispatch(doUserEmailVerifyFailure(message));
dispatch(doToast({ message })); dispatch(doToast({ message }));
} }
@ -625,11 +409,11 @@ class AppWithNavigationState extends React.Component {
dispatch( dispatch(
doToast({ doToast({
message: 'Invalid Verification URI', message: 'Invalid Verification URI',
}), })
); );
} }
} else { } else {
dispatchNavigateToUri(dispatch, nav, transformUrl(evt.url)); dispatchNavigateToUri(dispatch, nav, evt.url);
} }
} }
}; };
@ -641,7 +425,6 @@ class AppWithNavigationState extends React.Component {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
backgroundPlayEnabled: makeSelectClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED)(state), backgroundPlayEnabled: makeSelectClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED)(state),
hashChanged: selectHashChanged(state),
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state), keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
nav: state.nav, nav: state.nav,
toast: selectToast(state), toast: selectToast(state),
@ -651,7 +434,6 @@ const mapStateToProps = state => ({
emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state), emailVerifyErrorMessage: selectEmailVerifyErrorMessage(state),
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state), showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
user: selectUser(state), user: selectUser(state),
fullscreenMode: selectFullscreenMode(state),
}); });
export default connect(mapStateToProps)(AppWithNavigationState); export default connect(mapStateToProps)(AppWithNavigationState);

View file

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

View file

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

View file

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

View file

@ -19,4 +19,7 @@ const perform = dispatch => ({
resolveUri: uri => dispatch(doResolveUri(uri)), resolveUri: uri => dispatch(doResolveUri(uri)),
}); });
export default connect(select, perform)(ChannelIconItem); export default connect(
select,
perform
)(ChannelIconItem);

View file

@ -13,15 +13,10 @@ export default class ChannelIconItem extends React.PureComponent {
autothumbStyle.autothumbBlue, autothumbStyle.autothumbBlue,
autothumbStyle.autothumbLightBlue, autothumbStyle.autothumbLightBlue,
autothumbStyle.autothumbCyan, autothumbStyle.autothumbCyan,
autothumbStyle.autothumbTeal,
autothumbStyle.autothumbGreen, autothumbStyle.autothumbGreen,
autothumbStyle.autothumbYellow, autothumbStyle.autothumbYellow,
autothumbStyle.autothumbOrange, autothumbStyle.autothumbOrange,
autothumbStyle.autothumbDeepPurple,
autothumbStyle.autothumbAmber,
autothumbStyle.autothumbLime,
autothumbStyle.autothumbLightGreen,
autothumbStyle.autothumbDeepOrange,
autothumbStyle.autothumbBrown,
]; ];
state = { state = {
@ -51,7 +46,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.NextLbryGreen} /> <ActivityIndicator size={'small'} color={Colors.LbryGreen} />
</View> </View>
)} )}
<View <View
@ -63,7 +58,7 @@ export default class ChannelIconItem extends React.PureComponent {
> >
{isPlaceholder && ( {isPlaceholder && (
<View style={channelIconStyle.centered}> <View style={channelIconStyle.centered}>
<Text style={channelIconStyle.placeholderText}>{__('ALL')}</Text> <Text style={channelIconStyle.placeholderText}>ALL</Text>
</View> </View>
)} )}
{!isPlaceholder && thumbnail && ( {!isPlaceholder && thumbnail && (

View file

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

View file

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

View file

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

View file

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { CLAIM_VALUES, formatCredits, isNameValid } from 'lbry-redux'; import { CLAIM_VALUES, isURIValid } from 'lbry-redux';
import { ActivityIndicator, NativeModules, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { ActivityIndicator, Picker, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { logPublish } from 'utils/helper';
import Button from 'component/button'; import Button from 'component/button';
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
@ -14,7 +13,6 @@ 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,
@ -28,22 +26,15 @@ export default class ChannelSelector extends React.PureComponent {
} }
componentDidMount() { componentDidMount() {
const { channels = [], channelName, fetchChannelListMine, fetchingChannels } = this.props; const { channels, channelName, fetchChannelListMine, fetchingChannels } = this.props;
if ((!channels || channels.length === 0) && !fetchingChannels) { if (!channels.length && !fetchingChannels) {
fetchChannelListMine(); fetchChannelListMine();
} }
this.setState({ currentSelectedValue: channelName });
} }
componentWillReceiveProps(nextProps) { componentDidUpdate() {
const { channels: prevChannels = [], channelName: prevChannelName } = this.props; const { channelName } = this.props;
const { channels = [], channelName } = nextProps; if (this.state.currentSelectedValue !== channelName) {
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 });
} }
} }
@ -91,10 +82,10 @@ export default class ChannelSelector extends React.PureComponent {
newChannelName = newChannelName.slice(1); newChannelName = newChannelName.slice(1);
} }
if (newChannelName.trim().length > 0 && !isNameValid(newChannelName)) { if (newChannelName.trim().length > 0 && !isURIValid(newChannelName)) {
newChannelNameError = __('Your channel name contains invalid characters.'); newChannelNameError = 'Your channel name contains invalid characters.';
} else if (this.channelExists(newChannelName)) { } else if (this.channelExists(newChannelName)) {
newChannelNameError = __('You have already created a channel with the same name.'); newChannelNameError = 'You have already created a channel with the same name.';
} }
this.setState({ this.setState({
@ -123,21 +114,21 @@ export default class ChannelSelector extends React.PureComponent {
}; };
handleCreateChannelClick = () => { handleCreateChannelClick = () => {
const { balance, createChannel, getSync, onChannelChange, notify } = this.props; const { balance, createChannel, onChannelChange, notify } = this.props;
const { newChannelBid, newChannelName } = this.state; const { newChannelBid, newChannelName } = this.state;
if (newChannelName.trim().length === 0 || !isNameValid(newChannelName.substr(1), false)) { if (newChannelName.trim().length === 0 || !isURIValid(newChannelName.substr(1), false)) {
notify({ message: __('Your channel name contains invalid characters.') }); notify({ message: 'Your channel name contains invalid characters.' });
return; return;
} }
if (this.channelExists(newChannelName)) { if (this.channelExists(newChannelName)) {
notify({ message: __('You have already created a channel with the same name.') }); notify({ message: 'You have already created a channel with the same name.' });
return; return;
} }
if (newChannelBid > balance) { if (newChannelBid > balance) {
notify({ message: __('Deposit cannot be higher than your balance') }); notify({ message: 'Deposit cannot be higher than your balance' });
return; return;
} }
@ -148,25 +139,21 @@ export default class ChannelSelector extends React.PureComponent {
createChannelError: undefined, createChannelError: undefined,
}); });
const success = channelClaim => { const success = () => {
this.setState({ this.setState({
creatingChannel: false, creatingChannel: false,
addingChannel: false, addingChannel: false,
currentSelectedValue: channelName, currentSelectedValue: channelName,
showCreateChannel: false, showCreateChannel: false,
}); });
logPublish(channelClaim);
if (onChannelChange) { if (onChannelChange) {
onChannelChange(channelName); onChannelChange(channelName);
} }
// sync wallet
NativeModules.UtilityModule.getSecureValue(Constants.KEY_WALLET_PASSWORD).then(password => getSync(password));
}; };
const failure = () => { const failure = () => {
notify({ message: __('Unable to create channel due to an internal error.') }); notify({ message: 'Unable to create channel due to an internal error.' });
this.setState({ this.setState({
creatingChannel: false, creatingChannel: false,
}); });
@ -177,14 +164,12 @@ export default class ChannelSelector extends React.PureComponent {
channelExists = name => { channelExists = name => {
const { channels = [] } = this.props; const { channels = [] } = this.props;
if (channels) { for (let channel of channels) {
for (let channel of channels) { if (
if ( name.toLowerCase() === channel.name.toLowerCase() ||
name.toLowerCase() === channel.name.toLowerCase() || `@${name}`.toLowerCase() === channel.name.toLowerCase()
`@${name}`.toLowerCase() === channel.name.toLowerCase() ) {
) { return true;
return true;
}
} }
} }
@ -193,11 +178,8 @@ 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 { balance, enabled, fetchingChannels, channels = [], showAnonymous } = this.props; const { enabled, fetchingChannels, channels = [] } = this.props;
const pickerItems = (showAnonymous const pickerItems = [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].concat(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,
@ -220,11 +202,7 @@ export default class ChannelSelector extends React.PureComponent {
onValueChange={this.handlePickerValueChange} onValueChange={this.handlePickerValueChange}
> >
{pickerItems.map(item => ( {pickerItems.map(item => (
<Picker.Item <Picker.Item label={item} value={item} key={item} />
label={[Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].includes(item) ? __(item) : item}
value={item}
key={item}
/>
))} ))}
</Picker> </Picker>
@ -237,7 +215,7 @@ export default class ChannelSelector extends React.PureComponent {
style={channelSelectorStyle.channelNameInput} style={channelSelectorStyle.channelNameInput}
value={this.state.newChannelName} value={this.state.newChannelName}
onChangeText={this.handleNewChannelNameChange} onChangeText={this.handleNewChannelNameChange}
placeholder={__('Channel name')} placeholder={'Channel name'}
underlineColorAndroid={Colors.NextLbryGreen} underlineColorAndroid={Colors.NextLbryGreen}
/> />
</View> </View>
@ -245,38 +223,30 @@ export default class ChannelSelector extends React.PureComponent {
<Text style={channelSelectorStyle.inlineError}>{newChannelNameError}</Text> <Text style={channelSelectorStyle.inlineError}>{newChannelNameError}</Text>
)} )}
<View style={channelSelectorStyle.bidRow}> <View style={channelSelectorStyle.bidRow}>
<Text style={channelSelectorStyle.label}>{__('Deposit')}</Text> <Text style={channelSelectorStyle.label}>Deposit</Text>
<TextInput <TextInput
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.NextLbryGreen} />} {creatingChannel && <ActivityIndicator size={'small'} color={Colors.LbryGreen} />}
{!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} />
<Button <Button
style={channelSelectorStyle.createButton} style={channelSelectorStyle.createButton}
disabled={!(newChannelName.trim().length > 0 && newChannelBid > 0)} disabled={!(newChannelName.trim().length > 0 && newChannelBid > 0)}
text={__('Create')} text="Create"
onPress={this.handleCreateChannelClick} onPress={this.handleCreateChannelClick}
/> />
</View> </View>

View file

@ -11,16 +11,20 @@ import { selectShowNsfw } 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 ClaimList from './view'; import ClaimList from './view';
const select = state => ({ const select = (state, props) => {
claimSearchByQuery: selectClaimSearchByQuery(state), return {
lastPageReached: selectClaimSearchByQueryLastPageReached(state), claimSearchByQuery: selectClaimSearchByQuery(state),
loadingByQuery: selectFetchingClaimSearchByQuery(state), lastPageReached: selectClaimSearchByQueryLastPageReached(state),
loading: selectFetchingClaimSearch(state), loadingByQuery: selectFetchingClaimSearchByQuery(state),
showNsfwContent: selectShowNsfw(state), loading: selectFetchingClaimSearch(state),
}); };
};
const perform = dispatch => ({ const perform = dispatch => ({
claimSearch: options => dispatch(doClaimSearch(options)), claimSearch: options => dispatch(doClaimSearch(options)),
}); });
export default connect(select, perform)(ClaimList); export default connect(
select,
perform
)(ClaimList);

View file

@ -12,7 +12,7 @@ import claimListStyle from 'styles/claimList';
import discoverStyle from 'styles/discover'; import discoverStyle from 'styles/discover';
import moment from 'moment'; import moment from 'moment';
const horizontalLimit = 10; const horizontalLimit = 7;
const softLimit = 500; const softLimit = 500;
class ClaimList extends React.PureComponent { class ClaimList extends React.PureComponent {
@ -42,6 +42,7 @@ class ClaimList extends React.PureComponent {
tags: prevTags, tags: prevTags,
channelIds: prevChannelIds, channelIds: prevChannelIds,
time: prevTime, time: prevTime,
showNsfwContent,
} = prevProps; } = prevProps;
const { claimSearchByQuery, orderBy, tags, channelIds, time } = this.props; const { claimSearchByQuery, orderBy, tags, channelIds, time } = this.props;
@ -71,7 +72,7 @@ class ClaimList extends React.PureComponent {
} }
buildClaimSearchOptions() { buildClaimSearchOptions() {
const { orderBy, channelIds, showNsfwContent, tags, time } = this.props; const { orderBy = Constants.DEFAULT_ORDER_BY, channelIds, showNsfwContent, tags, time } = this.props;
const { currentPage, subscriptionsView } = this.state; const { currentPage, subscriptionsView } = this.state;
const options = { const options = {
@ -147,7 +148,7 @@ class ClaimList extends React.PureComponent {
renderMorePlaceholder = () => { renderMorePlaceholder = () => {
return ( return (
<TouchableOpacity style={discoverStyle.fileItemMore} onPress={this.onMorePressed}> <TouchableOpacity style={discoverStyle.fileItemMore} onPress={this.onMorePressed}>
<Text style={discoverStyle.moreText}>{__('more')}</Text> <Text style={discoverStyle.moreText}>more</Text>
<Icon style={discoverStyle.moreIcon} name={'angle-double-down'} color={Colors.White} size={16} /> <Icon style={discoverStyle.moreIcon} name={'angle-double-down'} color={Colors.White} size={16} />
</TouchableOpacity> </TouchableOpacity>
); );
@ -155,9 +156,7 @@ class ClaimList extends React.PureComponent {
verticalListEmptyComponent = () => { verticalListEmptyComponent = () => {
return ( return (
<Text style={claimListStyle.noContentText}> <Text style={claimListStyle.noContentText}>No content to display at this time. Please check back later.</Text>
{__('No content to display at this time. Please check back later.')}
</Text>
); );
}; };
@ -206,11 +205,7 @@ class ClaimList extends React.PureComponent {
const options = this.buildClaimSearchOptions(); const options = this.buildClaimSearchOptions();
const claimSearchKey = createNormalizedClaimSearchKey(options); const claimSearchKey = createNormalizedClaimSearchKey(options);
let uris = claimSearchByQuery[claimSearchKey]; const uris = claimSearchByQuery[claimSearchKey];
if (uris) {
uris = uris.filter(uri => uri && uri.length > 0);
}
if (Constants.ORIENTATION_VERTICAL === orientation) { if (Constants.ORIENTATION_VERTICAL === orientation) {
return ( return (

View file

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

View file

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

View file

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

View file

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

View file

@ -1,30 +1,24 @@
import React from 'react'; import React from 'react';
import { DrawerItems, SafeAreaView } from 'react-navigation'; import { DrawerItems, SafeAreaView } from 'react-navigation';
import { Image, ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { ScrollView, Text, TouchableOpacity, View } from 'react-native';
import Button from 'component/button';
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';
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': [
{ icon: 'at', label: 'Channels', route: Constants.DRAWER_ROUTE_CHANNEL_CREATOR },
{ icon: 'download', label: 'Library', route: Constants.DRAWER_ROUTE_MY_LBRY }, { icon: 'download', label: 'Library', route: Constants.DRAWER_ROUTE_MY_LBRY },
{ icon: 'cloud-upload-alt', label: 'Publishes', route: Constants.DRAWER_ROUTE_PUBLISHES }, { icon: 'cloud-upload-alt', label: 'Publishes', route: Constants.DRAWER_ROUTE_PUBLISHES },
{ icon: 'upload', label: 'New Publish', route: Constants.DRAWER_ROUTE_PUBLISH }, { icon: 'upload', label: 'New publish', route: Constants.DRAWER_ROUTE_PUBLISH },
], ],
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 },
@ -33,195 +27,57 @@ 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() {
Lbryio.getExchangeRates().then(rates => {
if (!isNaN(rates.LBC_USD)) {
this.setState({ usdExchangeRate: rates.LBC_USD });
}
});
}
getAvatarImageUrl = () => {
const { channels = [] } = this.props;
if (channels) {
// get the first channel thumbnail found. In the future, allow the user to select a default channel thumbnail?
for (let i = 0; i < channels.length; i++) {
if (channels[i].value && channels[i].value.thumbnail) {
return channels[i].value.thumbnail.url;
}
}
}
return null;
};
launchSignInFlow = () => {
// for now, sync flow (email, then password input) will be the default sign in flow
const { navigation } = this.props;
navigation.navigate({
routeName: 'Verification',
key: 'verification',
params: { syncFlow: true, signInFlow: true },
});
};
handleItemPress = routeName => {
const { navigation, notify, sdkReady } = this.props;
if (!sdkReady && routesRequiringSdkReady.includes(routeName)) {
if (navigation.closeDrawer) {
navigation.closeDrawer();
}
notify({
message: __('The background service is still initializing. Please try again when initialization is complete.'),
});
return;
}
navigation.navigate({ routeName: routeName });
};
render() { render() {
const { activeTintColor, balance, navigation, unclaimedRewardAmount, user, onItemPress } = this.props; const { activeTintColor, navigation, 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;
const signedIn = user && user.has_verified_email;
const avatarImageUrl = this.getAvatarImageUrl();
return ( return (
<View style={discoverStyle.drawerContentArea}> <ScrollView contentContainerStyle={discoverStyle.menuScrollContent}>
{false && ( <SafeAreaView style={discoverStyle.drawerContentContainer} forceInset={{ top: 'always', horizontal: 'never' }}>
<View style={discoverStyle.signInContainer}> {groupNames.map(groupName => {
{!signedIn && ( const menuItems = groupedMenuItems[groupName];
<Button
style={discoverStyle.signInButton} return (
theme={'light'} <View key={groupName} style={discoverStyle.menuGroup}>
text={__('Sign in')} {groupNames[3] !== groupName && (
onPress={this.launchSignInFlow} <Text key={`${groupName}-title`} style={discoverStyle.menuGroupName}>
/> {groupName}
)} </Text>
{signedIn && ( )}
<View style={discoverStyle.signedIn}> {menuItems.map(item => {
<View style={discoverStyle.signedInAvatar}> const focused =
{avatarImageUrl && ( activeItemKey === item.route ||
<Image (activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER &&
style={discoverStyle.signedInAvatarImage} item.route === Constants.DRAWER_ROUTE_DISCOVER) ||
resizeMode={'cover'} (activeItemKey === Constants.FULL_ROUTE_NAME_WALLET &&
source={{ uri: avatarImageUrl }} item.route === Constants.DRAWER_ROUTE_WALLET);
/> return (
)} <TouchableOpacity
{!avatarImageUrl && ( accessible
<Text style={channelIconStyle.autothumbCharacter}> accessibilityLabel={item.label}
{user.primary_email.substring(0, 1).toUpperCase()} style={[discoverStyle.menuItemTouchArea, focused ? discoverStyle.menuItemTouchAreaFocused : null]}
</Text> key={item.label}
)} onPress={() => navigation.navigate({ routeName: item.route })}
</View> delayPressIn={0}
<Text style={discoverStyle.signedInEmail} numberOfLines={1}> >
{user.primary_email} <View style={discoverStyle.menuItemIcon}>
</Text> <Icon name={item.icon} size={16} solid={item.solid} color={focused ? activeTintColor : null} />
</View>
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
{item.label}
</Text>
</TouchableOpacity>
);
})}
</View> </View>
)} );
</View> })}
)} </SafeAreaView>
</ScrollView>
<ScrollView contentContainerStyle={discoverStyle.menuScrollContent}>
<SafeAreaView
style={discoverStyle.drawerContentContainer}
forceInset={{ top: 'always', horizontal: 'never' }}
>
{!signedIn && (
<TouchableOpacity
accessible
accessibilityLabel={__('Sign In')}
onPress={this.launchSignInFlow}
delayPressIn={0}
style={[discoverStyle.signInMenuItem, discoverStyle.signInMenuItemButton]}
>
<Text style={discoverStyle.signInMenuItemButtonText}>{__('SIGN IN')}</Text>
</TouchableOpacity>
)}
{signedIn && (
<View style={[discoverStyle.signInMenuItem, discoverStyle.signInMenuItemBorder]}>
<Text style={discoverStyle.signInMenuItemText} numberOfLines={1}>
{user.primary_email.toUpperCase()}
</Text>
</View>
)}
{groupNames.map(groupName => {
const menuItems = groupedMenuItems[groupName];
return (
<View key={groupName} style={discoverStyle.menuGroup}>
{groupNames[3] !== groupName && (
<Text key={`${groupName}-title`} style={discoverStyle.menuGroupName}>
{__(groupName)}
</Text>
)}
{menuItems.map(item => {
const focused =
activeItemKey === item.route ||
(activeItemKey === Constants.FULL_ROUTE_NAME_DISCOVER &&
item.route === Constants.DRAWER_ROUTE_SUBSCRIPTIONS) ||
(activeItemKey === Constants.FULL_ROUTE_NAME_WALLET &&
item.route === Constants.DRAWER_ROUTE_WALLET);
return (
<TouchableOpacity
accessible
accessibilityLabel={__(item.label)}
style={[
discoverStyle.menuItemTouchArea,
focused ? discoverStyle.menuItemTouchAreaFocused : null,
]}
key={item.label}
onPress={() => this.handleItemPress(item.route)}
delayPressIn={0}
>
<View style={discoverStyle.menuItemIcon}>
<Icon
name={item.icon}
size={16}
solid={item.solid}
color={focused ? activeTintColor : null}
/>
</View>
<Text style={[discoverStyle.menuItem, focused ? discoverStyle.menuItemFocused : null]}>
{__(item.label)}
{item.label === 'Wallet' && this.state.usdExchangeRate > 0 && (
<Text> ({formatUsd(parseFloat(balance) * parseFloat(this.state.usdExchangeRate))})</Text>
)}
{item.label === 'Rewards' && this.state.usdExchangeRate > 0 && (
<Text>
{' '}
({formatUsd(parseFloat(unclaimedRewardAmount) * parseFloat(this.state.usdExchangeRate))})
</Text>
)}
</Text>
</TouchableOpacity>
);
})}
</View>
);
})}
</SafeAreaView>
</ScrollView>
</View>
); );
} }
} }

View file

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

View file

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

View file

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

View file

@ -10,16 +10,13 @@ import {
makeSelectClaimIsNsfw, makeSelectClaimIsNsfw,
makeSelectShortUrlForUri, makeSelectShortUrlForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { doSetPlayerVisible } from 'redux/actions/drawer'; import { 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';
const select = (state, props) => ({ const select = (state, props) => ({
blackListedOutpoints: selectBlackListedOutpoints(state),
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
filteredOutpoints: selectFilteredOutpoints(state),
metadata: makeSelectMetadataForUri(props.uri)(state), metadata: makeSelectMetadataForUri(props.uri)(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state), rewardedContentClaimIds: selectRewardContentClaimIds(state),
isResolvingUri: makeSelectIsUriResolving(props.uri)(state), isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
@ -32,7 +29,9 @@ 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(select, perform)(FileItem); export default connect(
select,
perform
)(FileItem);

View file

@ -30,22 +30,20 @@ class FileItem extends React.PureComponent {
} }
navigateToFileUri = () => { navigateToFileUri = () => {
const { claim, navigation, uri, shortUrl } = this.props; const { navigation, uri, shortUrl } = this.props;
const normalizedUri = normalizeURI(uri); const normalizedUri = normalizeURI(uri);
if (NativeModules.Firebase) { if (NativeModules.Firebase) {
NativeModules.Firebase.track('explore_click', { uri: normalizedUri, short_url: shortUrl }); NativeModules.Firebase.track('explore_click', { uri: normalizedUri, short_url: shortUrl });
} }
navigateToUri(navigation, shortUrl || uri, null, false, claim ? claim.permanent_url : null); navigateToUri(navigation, shortUrl || uri);
}; };
render() { render() {
const { const {
blackListedOutpoints,
claim, claim,
title, title,
thumbnail, thumbnail,
fileInfo, fileInfo,
filteredOutpoints,
metadata, metadata,
isResolvingUri, isResolvingUri,
rewardedContentClaimIds, rewardedContentClaimIds,
@ -56,26 +54,11 @@ class FileItem extends React.PureComponent {
obscureNsfw, obscureNsfw,
showDetails, showDetails,
compactView, compactView,
setPlayerVisible,
titleBeforeThumbnail, titleBeforeThumbnail,
} = this.props; } = this.props;
if (claim && claim.value_type === 'channel') { if (claim && claim.value_type === 'channel') {
// don't display channels in the lists on the Your tags page // don't display channels in the lists on the Explore page
return null;
}
let shouldHide = false;
if (blackListedOutpoints || filteredOutpoints) {
const outpointsToHide = !blackListedOutpoints
? filteredOutpoints
: blackListedOutpoints.concat(filteredOutpoints);
shouldHide = outpointsToHide.some(
outpoint => outpoint && outpoint.txid === claim.txid && outpoint.nout === claim.nout,
);
}
if (shouldHide) {
// don't display blacklisted or filtered outpoints on the Your tags page
return null; return null;
} }
@ -110,12 +93,7 @@ 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 <FilePrice uri={uri} style={discoverStyle.filePriceContainer} textStyle={discoverStyle.filePriceText} />
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}>
@ -132,18 +110,11 @@ class FileItem extends React.PureComponent {
style={discoverStyle.channelName} style={discoverStyle.channelName}
text={channelName} text={channelName}
onPress={() => { onPress={() => {
navigateToUri( navigateToUri(navigation, normalizeURI(shortChannelUri || fullChannelUri));
navigation,
normalizeURI(shortChannelUri || fullChannelUri),
null,
false,
fullChannelUri,
setPlayerVisible,
);
}} }}
/> />
)} )}
{!channelName && !isResolvingUri && <Text style={discoverStyle.anonChannelName}>{__('Anonymous')}</Text>} {!channelName && !isResolvingUri && <Text style={discoverStyle.anonChannelName}>Anonymous</Text>}
<DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} /> <DateTime style={discoverStyle.dateTime} textStyle={discoverStyle.dateTimeText} timeAgo uri={uri} />
</View> </View>
)} )}

View file

@ -61,8 +61,6 @@ 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) {
style = fileItemMediaStyle.thumbnail; style = fileItemMediaStyle.thumbnail;
@ -76,7 +74,7 @@ class FileItemMedia extends React.PureComponent {
resizeMode={this.getFastImageResizeMode(resizeMode)} resizeMode={this.getFastImageResizeMode(resizeMode)}
style={fileItemMediaStyle.image} style={fileItemMediaStyle.image}
/> />
{duration > 0 && ( {duration && (
<VideoDuration <VideoDuration
duration={duration} duration={duration}
style={fileItemMediaStyle.duration} style={fileItemMediaStyle.duration}
@ -105,7 +103,7 @@ class FileItemMedia extends React.PureComponent {
.toUpperCase()} .toUpperCase()}
</Text> </Text>
)} )}
{duration > 0 && ( {duration && (
<VideoDuration <VideoDuration
duration={duration} duration={duration}
style={fileItemMediaStyle.duration} style={fileItemMediaStyle.duration}

View file

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

View file

@ -10,7 +10,6 @@ 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';
@ -33,7 +32,9 @@ 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(select, perform)(FileListItem); export default connect(
select,
perform
)(FileListItem);

View file

@ -1,15 +1,10 @@
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, getDownloadProgress, getStorageForFileInfo } from 'utils/helper'; import { navigateToUri, formatBytes } 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 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';
@ -17,9 +12,22 @@ import ProgressBar from 'component/progressBar';
import fileListStyle from 'styles/fileList'; import fileListStyle from 'styles/fileList';
class FileListItem extends React.PureComponent { class FileListItem extends React.PureComponent {
state = { getStorageForFileInfo = fileInfo => {
autoStyle: null, if (!fileInfo.completed) {
url: null, 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 => {
@ -27,32 +35,15 @@ class FileListItem extends React.PureComponent {
}; };
componentDidMount() { componentDidMount() {
const { claim, resolveUri, uri, batchResolve } = this.props; const { claim, resolveUri, uri } = this.props;
if (!claim && !batchResolve) { if (!claim) {
resolveUri(uri); resolveUri(uri);
} }
this.setState({
autoStyle:
ChannelIconItem.AUTO_THUMB_STYLES[Math.floor(Math.random() * ChannelIconItem.AUTO_THUMB_STYLES.length)],
});
}
componentDidUpdate() {
const { claim, resolveUri, uri, batchResolve } = this.props;
if (!claim && uri !== this.state.url && !batchResolve) {
this.setState({ url: uri }, () => resolveUri(uri));
}
} }
defaultOnPress = () => { defaultOnPress = () => {
const { autoplay, claim, featuredResult, navigation, uri, shortUrl } = this.props; const { autoplay, navigation, uri, shortUrl } = this.props;
navigateToUri(navigation, shortUrl || uri, { autoplay });
if (featuredResult && !claim) {
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_PUBLISH, params: { vanityUrl: uri.trim() } });
} else {
navigateToUri(navigation, shortUrl || uri, { autoplay }, false, claim ? claim.permanent_url : null);
}
}; };
onPressHandler = () => { onPressHandler = () => {
@ -80,7 +71,6 @@ class FileListItem extends React.PureComponent {
onPress, onPress,
navigation, navigation,
rewardedContentClaimIds, rewardedContentClaimIds,
setPlayerVisible,
thumbnail, thumbnail,
hideChannel, hideChannel,
onLongPress, onLongPress,
@ -99,13 +89,9 @@ 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;
@ -115,60 +101,24 @@ 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.concat(filteredOutpoints);
? filteredOutpoints shouldHide = outpointsToHide.some(outpoint => outpoint.txid === claim.txid && outpoint.nout === claim.nout);
: blackListedOutpoints.concat(filteredOutpoints);
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?
// shouldHide = 'channel' === claim.value_type; // shouldHide = 'channel' === claim.value_type;
} }
if (shouldHide || (!isResolvingUri && !claim && !featuredResult)) { if (shouldHide || (!isResolvingUri && !claim) || (featuredResult && !isResolvingUri && !claim && !title && !name)) {
return null; return null;
} }
const actualHideChannel = !isRepost && hideChannel;
const isChannel = name && name.startsWith('@');
const hasThumbnail = !!thumbnail;
return ( return (
<View> <View style={style}>
{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, isChannel ? fileListStyle.channelContainer : null]} style={style}
onPress={this.onPressHandler} onPress={this.onPressHandler}
onLongPress={() => { onLongPress={() => {
if (onLongPress) { if (onLongPress) {
@ -176,55 +126,21 @@ class FileListItem extends React.PureComponent {
} }
}} }}
> >
{!isChannel && ( <FileItemMedia
<FileItemMedia style={fileListStyle.thumbnail}
style={fileListStyle.thumbnail} duration={duration}
duration={duration} resizeMode="cover"
resizeMode="cover" title={title || name}
title={title || name || normalizeURI(uri).substring(7)} thumbnail={thumbnail}
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 <Icon style={fileListStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
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}>
@ -237,7 +153,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.NextLbryGreen} /> <ActivityIndicator size={'small'} color={featuredResult ? Colors.White : Colors.LbryGreen} />
</View> </View>
)} )}
</View> </View>
@ -245,42 +161,25 @@ class FileListItem extends React.PureComponent {
{(title || name) && ( {(title || name) && (
<View style={fileListStyle.titleContainer}> <View style={fileListStyle.titleContainer}>
<Text <Text style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title}>
style={featuredResult ? fileListStyle.featuredTitle : fileListStyle.title} {this.formatTitle(title) || this.formatTitle(name)}
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>
)} )}
{channel && !hideChannel && (
{featuredResult && !isResolving && !claim && (
<View style={fileListStyle.titleContainer}>
<Text style={fileListStyle.featuredTitle}>{__('Nothing here. Publish something!')}</Text>
</View>
)}
{(channel || isChannel) && !actualHideChannel && (
<Link <Link
style={fileListStyle.publisher} style={fileListStyle.publisher}
text={isChannel ? name : channel} text={channel}
onPress={() => { onPress={() => {
navigateToUri( navigateToUri(navigation, normalizeURI(shortChannelUri || fullChannelUri));
navigation,
normalizeURI(isChannel ? uri : shortChannelUri || fullChannelUri),
null,
false,
isChannel ? claim && claim.permanent_url : fullChannelUri,
setPlayerVisible,
);
}} }}
/> />
)} )}
<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}>{getStorageForFileInfo(fileInfo)}</Text> <Text style={fileListStyle.infoText}>{this.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>
@ -293,7 +192,7 @@ class FileListItem extends React.PureComponent {
color={Colors.NextLbryGreen} color={Colors.NextLbryGreen}
height={3} height={3}
style={fileListStyle.progress} style={fileListStyle.progress}
progress={getDownloadProgress(fileInfo)} progress={this.getDownloadProgress(fileInfo)}
/> />
)} )}
</View> </View>

View file

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

View file

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

View file

@ -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'; // eslint-disable-line node/no-deprecated-api import Constants from 'constants';
import FloatingWalletBalance from './view'; import FloatingWalletBalance from './view';
const select = state => ({ const select = state => ({
@ -11,4 +11,7 @@ const select = state => ({
rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state), rewardsNotInterested: makeSelectClientSetting(Constants.SETTING_REWARDS_NOT_INTERESTED)(state),
}); });
export default connect(select, null)(FloatingWalletBalance); export default connect(
select,
null
)(FloatingWalletBalance);

View file

@ -34,7 +34,7 @@ class FloatingWalletBalance extends React.PureComponent<Props> {
<Icon name="coins" size={12} style={floatingButtonStyle.balanceIcon} /> <Icon name="coins" size={12} style={floatingButtonStyle.balanceIcon} />
{isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />} {isNaN(balance) && <ActivityIndicator size="small" color={Colors.White} />}
{(!isNaN(balance) || balance === 0) && ( {(!isNaN(balance) || balance === 0) && (
<Text style={floatingButtonStyle.text}>{formatCredits(parseFloat(balance), 1, true)}</Text> <Text style={floatingButtonStyle.text}>{formatCredits(parseFloat(balance), 0, true)}</Text>
)} )}
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -36,7 +36,7 @@ class MediaPlayer extends React.PureComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
buffering: true, buffering: false,
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: true, paused: !props.autoPlay,
fullscreenMode: false, fullscreenMode: false,
areControlsVisible: false, areControlsVisible: true,
controlsTimeout: -1, controlsTimeout: -1,
seekerOffset: 0, seekerOffset: 0,
seekerPosition: 0, seekerPosition: 0,
@ -87,9 +87,7 @@ 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,
}); });
@ -102,17 +100,13 @@ 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 (claim && data.currentTime > 0 && Math.floor(data.currentTime) % positionSaveInterval === 0) { if (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);
} }
@ -173,7 +167,7 @@ class MediaPlayer extends React.PureComponent {
togglePlay = () => { togglePlay = () => {
this.showPlayerControls(); this.showPlayerControls();
this.setState({ paused: !this.state.paused }, this.updateBackgroundMediaNotification); this.setState({ paused: !this.state.paused }, this.handlePausedState);
}; };
handlePausedState = () => { handlePausedState = () => {
@ -194,7 +188,7 @@ class MediaPlayer extends React.PureComponent {
}; };
onEnd = () => { onEnd = () => {
this.setState({ paused: true }, this.updateBackgroundMediaNotification); this.setState({ paused: true });
if (this.props.onPlaybackFinished) { if (this.props.onPlaybackFinished) {
this.props.onPlaybackFinished(); this.props.onPlaybackFinished();
} }
@ -333,10 +327,6 @@ 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());
@ -351,12 +341,6 @@ 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;
@ -378,26 +362,14 @@ 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>
<View style={mediaPlayerStyle.midControls}> <TouchableOpacity style={mediaPlayerStyle.playPauseButton} onPress={this.togglePlay}>
<TouchableOpacity style={mediaPlayerStyle.rewind10} onPress={() => this.handleSeek(-10)}> {this.state.paused && <Icon name="play" size={40} color="#ffffff" />}
<Icon name="undo" size={24} color={Colors.White} /> {!this.state.paused && <Icon name="pause" size={40} color="#ffffff" />}
<Text style={[mediaPlayerStyle.midControlText, mediaPlayerStyle.leftMidControlText]}>10</Text> </TouchableOpacity>
</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={Colors.White} />} {this.state.fullscreenMode && <Icon name="compress" size={16} color="#ffffff" />}
{!this.state.fullscreenMode && <Icon name="expand" size={16} color={Colors.White} />} {!this.state.fullscreenMode && <Icon name="expand" size={16} color="#ffffff" />}
</TouchableOpacity> </TouchableOpacity>
<Text style={mediaPlayerStyle.elapsedDuration}>{this.formatTime(this.state.currentTime)}</Text> <Text style={mediaPlayerStyle.elapsedDuration}>{this.formatTime(this.state.currentTime)}</Text>
@ -449,7 +421,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
); );
} }
@ -468,7 +440,6 @@ 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}
@ -479,7 +450,6 @@ 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 && (

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ import Icon from 'react-native-vector-icons/FontAwesome5';
import Tag from 'component/tag'; import Tag from 'component/tag';
import TagSearch from 'component/tagSearch'; import TagSearch from 'component/tagSearch';
import modalTagSelectorStyle from 'styles/modalTagSelector'; import modalTagSelectorStyle from 'styles/modalTagSelector';
import { __ } from 'utils/helper';
const minimumTags = 2; const minimumTags = 2;
@ -54,9 +55,9 @@ export default class ModalTagSelector extends React.PureComponent {
return ( return (
<TouchableOpacity style={modalTagSelectorStyle.overlay} activeOpacity={1} onPress={onOverlayPress}> <TouchableOpacity style={modalTagSelectorStyle.overlay} activeOpacity={1} onPress={onOverlayPress}>
<TouchableOpacity style={modalTagSelectorStyle.container} activeOpacity={1}> <View style={modalTagSelectorStyle.container}>
<View style={modalTagSelectorStyle.titleRow}> <View style={modalTagSelectorStyle.titleRow}>
<Text style={modalTagSelectorStyle.title}>{__('Customize your tags')}</Text> <Text style={modalTagSelectorStyle.title}>Customize your tags</Text>
</View> </View>
<View style={modalTagSelectorStyle.tagList}> <View style={modalTagSelectorStyle.tagList}>
{tags && {tags &&
@ -72,9 +73,9 @@ export default class ModalTagSelector extends React.PureComponent {
</View> </View>
<TagSearch handleAddTag={this.handleAddTag} selectedTags={tags} /> <TagSearch handleAddTag={this.handleAddTag} selectedTags={tags} />
<View style={modalTagSelectorStyle.buttons}> <View style={modalTagSelectorStyle.buttons}>
<Button style={modalTagSelectorStyle.doneButton} text={__('Done')} onPress={onDonePress} /> <Button style={modalTagSelectorStyle.doneButton} text={'Done'} onPress={onDonePress} />
</View> </View>
</TouchableOpacity> </View>
</TouchableOpacity> </TouchableOpacity>
); );
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,35 +1,25 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
doResolveUris,
doResolvedSearch,
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectResolvedRecommendedContentForUri, makeSelectRecommendedContentForUri,
selectResolvingUris, makeSelectTitleForUri,
selectIsSearching, selectIsSearching,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings'; import { doNativeSearch } from 'redux/actions/performance';
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)), search: query => dispatch(doNativeSearch(query, 20, undefined, true)),
searchRecommended: (query, claimId, nsfw) =>
dispatch(doResolvedSearch(query, RESULT_SIZE, undefined, true, { related_to: claimId }, nsfw)),
}); });
export default connect(select, perform)(RelatedContent); export default connect(
select,
perform
)(RelatedContent);

View file

@ -4,42 +4,68 @@ 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 {
constructor() {
super();
this.didSearch = undefined;
}
componentDidMount() { componentDidMount() {
const { title, claimId, searchRecommended, showNsfwContent } = this.props; this.getRecommendedContent();
if (title && claimId) { }
searchRecommended(title, claimId, showNsfwContent);
componentDidUpdate(prevProps) {
const { claim, uri } = this.props;
if (uri !== prevProps.uri) {
this.didSearch = false;
}
if (claim && !this.didSearch) {
this.getRecommendedContent();
} }
} }
shouldComponentUpdate(nextProps, nextState) { getRecommendedContent() {
const { isSearching, recommendedContent } = nextProps; const { search, title } = this.props;
return isSearching || (!isSearching && recommendedContent && recommendedContent.length > 0);
if (title) {
search(title);
this.didSearch = true;
}
} }
didSearch;
render() { render() {
const { isSearching, recommendedContent, navigation, urlOpenHandler, uri, fullUri } = this.props; const { recommendedContent, isSearching, navigation, uri, fullUri } = this.props;
if (!isSearching && (!recommendedContent || recommendedContent.length === 0)) {
return null;
}
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.map(result => ( recommendedContent
<ClaimResultItem .filter(recommendedUri => recommendedUri !== normalizeURI(fullUri))
style={fileListStyle.item} .map(recommendedUri => (
uri={result ? normalizeURI(`${result.name}#${result.claimId}`) : null} <FileListItem
urlOpenHandler={urlOpenHandler} style={fileListStyle.item}
key={result.claimId} key={recommendedUri}
result={result} uri={recommendedUri}
navigation={navigation} navigation={navigation}
autoplay autoplay
/> />
))} ))}
{isSearching && (
<ActivityIndicator size="small" color={Colors.NextLbryGreen} style={relatedContentStyle.loading} />
)}
</View> </View>
); );
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,7 +6,6 @@ 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) => ({
@ -14,13 +13,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(select, perform)(SuggestedSubscriptionItem); export default connect(
select,
perform
)(SuggestedSubscriptionItem);

View file

@ -1,37 +1,25 @@
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, TouchableOpacity, View } from 'react-native'; import { ActivityIndicator, FlatList, Image, Text, 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 SubscribeButtonOverlay from 'component/subscribeButtonOverlay'; import SubscribeButton from 'component/subscribeButton';
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;
@ -40,60 +28,52 @@ 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.NextLbryGreen} /> <ActivityIndicator size={'small'} color={Colors.LbryGreen} />
</View> </View>
); );
} }
return ( return (
<TouchableOpacity style={subscriptionsStyle.suggestedItem}> <View style={subscriptionsStyle.suggestedItem}>
<View style={[subscriptionsStyle.suggestedItemThumbnailContainer, this.state.autoStyle]}> <View style={subscriptionsStyle.suggestedItemThumbnailContainer}>
{hasThumbnail && ( <Image
<Image style={subscriptionsStyle.suggestedItemThumbnail} resizeMode={'cover'} source={{ uri: thumbnail }} /> style={subscriptionsStyle.suggestedItemThumbnail}
)} resizeMode={'cover'}
{!hasThumbnail && ( source={thumbnail ? { uri: thumbnail } : require('../../assets/default_avatar.jpg')}
<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}>
<Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={2}> {title && (
{title || claim.name} <Text style={subscriptionsStyle.suggestedItemTitle} numberOfLines={1}>
</Text> {title}
</Text>
)}
{claim && (
<Link
style={subscriptionsStyle.suggestedItemName}
numberOfLines={1}
text={claim.name}
onPress={() => navigateToUri(navigation, normalizeURI(shortUrl || uri))}
/>
)}
{tags && ( {tags && (
<View style={subscriptionsStyle.suggestedItemTagList}> <View style={subscriptionsStyle.suggestedItemTagList}>
{tags && {tags &&
tags tags
.slice(0, 1) .slice(0, 3)
.map(tag => ( .map(tag => (
<Tag <Tag style={subscriptionsStyle.tag} key={tag} name={tag} navigation={navigation} truncate />
numberOfLines={1}
onPress={this.handleItemPress}
style={subscriptionsStyle.tag}
key={tag}
name={tag}
navigation={navigation}
truncate
/>
))} ))}
</View> </View>
)} )}
</View> </View>
{claim && ( <SubscribeButton style={subscriptionsStyle.suggestedItemSubscribe} uri={normalizeURI(uri)} />
<SubscribeButtonOverlay </View>
claim={claim}
style={subscriptionsStyle.suggestedItemSubscribeOverlay}
uri={normalizeURI(claim.permanent_url)}
/>
)}
</TouchableOpacity>
); );
} }
} }

View file

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

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { ActivityIndicator, SectionList, Text, View } from 'react-native'; import { ActivityIndicator, FlatList, SectionList, Text, View } from 'react-native';
import { MATURE_TAGS, createNormalizedClaimSearchKey, normalizeURI } from 'lbry-redux'; import { createNormalizedClaimSearchKey, normalizeURI } from 'lbry-redux';
import { navigateToUri } from 'utils/helper'; import { __, navigateToUri } from 'utils/helper';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem'; import SuggestedSubscriptionItem from 'component/suggestedSubscriptionItem';
import Colors from 'styles/colors'; import Colors from 'styles/colors';
@ -16,16 +16,13 @@ class SuggestedSubscriptions extends React.PureComponent {
}; };
componentDidMount() { componentDidMount() {
const { claimSearch, followedTags, showNsfwContent } = this.props; const { claimSearch, followedTags } = this.props;
const options = { const options = {
any_tags: _.shuffle(followedTags.map(tag => tag.name)).slice(0, 3), any_tags: _.shuffle(followedTags.map(tag => tag.name)).slice(0, 3),
page: 1, page: 1,
no_totals: true, no_totals: true,
claim_type: 'channel', claim_type: 'channel',
}; };
if (!showNsfwContent) {
options.not_tags = MATURE_TAGS;
}
this.setState({ options }); this.setState({ options });
claimSearch(options); claimSearch(options);
} }
@ -38,7 +35,7 @@ class SuggestedSubscriptions extends React.PureComponent {
const suggestedUris = suggested ? suggested.map(suggested => suggested.uri) : []; const suggestedUris = suggested ? suggested.map(suggested => suggested.uri) : [];
return [ return [
{ {
title: __('Suggested channels'), title: __('Tags you follow'),
data: claimSearchUris ? claimSearchUris.filter(uri => !suggestedUris.includes(uri)) : [], data: claimSearchUris ? claimSearchUris.filter(uri => !suggestedUris.includes(uri)) : [],
}, },
{ {
@ -49,7 +46,7 @@ class SuggestedSubscriptions extends React.PureComponent {
}; };
render() { render() {
const { suggested, inModal, loading, navigation } = this.props; const { suggested, loading, navigation } = this.props;
if (loading) { if (loading) {
return ( return (
@ -61,10 +58,8 @@ class SuggestedSubscriptions extends React.PureComponent {
return ( return (
<SectionList <SectionList
style={inModal ? subscriptionsStyle.modalScrollContainer : subscriptionsStyle.scrollContainer} style={subscriptionsStyle.scrollContainer}
contentContainerStyle={ contentContainerStyle={subscriptionsStyle.suggestedScrollPadding}
inModal ? subscriptionsStyle.modalSuggestedScrollContent : subscriptionsStyle.suggestedScrollContent
}
renderItem={({ item, index, section }) => ( renderItem={({ item, index, section }) => (
<SuggestedSubscriptionItem key={item} uri={normalizeURI(item)} navigation={navigation} /> <SuggestedSubscriptionItem key={item} uri={normalizeURI(item)} navigation={navigation} />
)} )}

View file

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

View file

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

View file

@ -30,7 +30,7 @@ export default class Tag extends React.PureComponent {
}; };
render() { render() {
const { name, numberOfLines, onPress, style, type, truncate } = this.props; const { name, onPress, style, type, truncate } = this.props;
let styles = []; let styles = [];
if (style) { if (style) {
@ -50,9 +50,7 @@ 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} numberOfLines={numberOfLines}> <Text style={tagStyle.text}>{truncate ? formatTagName(name) : name}</Text>
{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>

View file

@ -67,14 +67,13 @@ export default class TagSearch extends React.PureComponent {
}; };
render() { render() {
const { editable, name, style, type, selectedTags = [], showNsfwTags } = this.props; const { name, style, type, selectedTags = [], showNsfwTags } = this.props;
return ( return (
<View> <View>
<TextInput <TextInput
editable={editable}
style={tagStyle.searchInput} style={tagStyle.searchInput}
placeholder={__('Search for more tags')} placeholder={'Search for more tags'}
underlineColorAndroid={Colors.NextLbryGreen} underlineColorAndroid={Colors.NextLbryGreen}
value={this.state.tag} value={this.state.tag}
numberOfLines={1} numberOfLines={1}
@ -89,7 +88,7 @@ export default class TagSearch extends React.PureComponent {
</KeyboardAvoidingView> </KeyboardAvoidingView>
{showNsfwTags && ( {showNsfwTags && (
<View style={tagStyle.nsfwTagsContainer}> <View style={tagStyle.nsfwTagsContainer}>
<Text style={tagStyle.nsfwTagsTitle}>{__('Mature tags')}</Text> <Text style={tagStyle.nsfwTagsTitle}>Mature tags</Text>
<View style={tagStyle.tagResultsList}> <View style={tagStyle.tagResultsList}>
{MATURE_TAGS.map(tag => ( {MATURE_TAGS.map(tag => (
<Tag <Tag

View file

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

View file

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

View file

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

View file

@ -4,11 +4,8 @@ import {
selectSearchState as selectSearch, selectSearchState as selectSearch,
selectSearchValue, selectSearchValue,
selectSearchSuggestions, selectSearchSuggestions,
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 UriBar from './view'; import UriBar from './view';
const select = state => { const select = state => {
@ -19,16 +16,14 @@ const select = state => {
query: selectSearchValue(state), query: selectSearchValue(state),
currentRoute: selectCurrentRoute(state), currentRoute: selectCurrentRoute(state),
suggestions: selectSearchSuggestions(state), suggestions: selectSearchSuggestions(state),
showUriBarSuggestions: makeSelectClientSetting(SETTINGS.SHOW_URI_BAR_SUGGESTIONS)(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);

View file

@ -20,10 +20,6 @@ class UriBarItem extends React.PureComponent {
icon = <Icon name="search" size={18} />; icon = <Icon name="search" size={18} />;
break; break;
case SEARCH_TYPES.TAG:
icon = <Icon name="hashtag" size={18} />;
break;
case SEARCH_TYPES.FILE: case SEARCH_TYPES.FILE:
default: default:
icon = <Icon name="file" size={18} />; icon = <Icon name="file" size={18} />;
@ -41,7 +37,6 @@ class UriBarItem extends React.PureComponent {
{type === SEARCH_TYPES.SEARCH && `Search for '${value}'`} {type === SEARCH_TYPES.SEARCH && `Search for '${value}'`}
{type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`} {type === SEARCH_TYPES.CHANNEL && `View the @${shorthand} channel`}
{type === SEARCH_TYPES.FILE && `View content at ${value}`} {type === SEARCH_TYPES.FILE && `View content at ${value}`}
{type === SEARCH_TYPES.TAG && `Explore the '${value}' tag`}
</Text> </Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>

View file

@ -1,44 +1,37 @@
// @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 { Alert, Dimensions, FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native'; import { FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
import { navigateToUri, transformUrl } from 'utils/helper'; import { navigateToUri } 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
textInput = null; textInput = null;
keyboardDidShowListener = null;
keyboardDidHideListener = null; keyboardDidHideListener = null;
changeTextTimeout = -1;
state = { state = {
changeTextTimeout: null,
currentValue: null, currentValue: null,
inputText: null, inputText: null,
focused: false, focused: false,
keyboardHeight: 0,
selection: undefined, // TODO: Add a setting to enable / disable direct search?
directSearch: true,
}; };
componentDidMount() { componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide); this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
this.setState({ selection: { start: 0, end: 0 } }); this.setSelection();
} }
componentWillUnmount() { componentWillUnmount() {
if (this.keyboardDidShowListener) {
this.keyboardDidShowListener.remove();
}
if (this.keyboardDidHideListener) { if (this.keyboardDidHideListener) {
this.keyboardDidHideListener.remove(); this.keyboardDidHideListener.remove();
} }
@ -54,37 +47,32 @@ class UriBar extends React.PureComponent {
} }
handleChangeText = text => { handleChangeText = text => {
this.setState({ selection: undefined });
const newValue = text || ''; const newValue = text || '';
clearTimeout(this.changeTextTimeout); clearTimeout(this.state.changeTextTimeout);
const { updateSearchQuery, onSearchSubmitted, showUriBarSuggestions, navigation } = this.props; const { updateSearchQuery, onSearchSubmitted, navigation } = this.props;
this.changeTextTimeout = -1; let timeout = setTimeout(() => {
if (!showUriBarSuggestions) { if (text.trim().length === 0) {
this.changeTextTimeout = setTimeout(() => { // don't do anything if the text is empty
if (text.trim().length === 0) { return;
// don't do anything if the text is empty }
return;
}
if (!text.startsWith('lbry://')) {
// not a URI input, so this is a search, perform a direct search
updateSearchQuery(text);
if (onSearchSubmitted) {
onSearchSubmitted(text);
} else {
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text } });
}
}
}, UriBar.INPUT_TIMEOUT);
} else {
updateSearchQuery(text); updateSearchQuery(text);
}
this.setState({ inputText: newValue, currentValue: newValue }); if (!text.startsWith('lbry://')) {
// not a URI input, so this is a search, perform a direct search
if (onSearchSubmitted) {
onSearchSubmitted(text);
} else {
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: text } });
}
}
}, UriBar.INPUT_TIMEOUT);
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
}; };
handleItemPress = item => { handleItemPress = item => {
const { navigation, onSearchSubmitted, setPlayerVisible, updateSearchQuery } = this.props; const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
const { type, value } = item; const { type, value } = item;
Keyboard.dismiss(); Keyboard.dismiss();
@ -98,52 +86,35 @@ class UriBar extends React.PureComponent {
return; return;
} }
navigation.navigate({ navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value } });
routeName: Constants.DRAWER_ROUTE_SEARCH,
key: 'searchPage',
params: { searchQuery: value },
});
} else if (SEARCH_TYPES.TAG === type) {
navigation.navigate({
routeName: Constants.DRAWER_ROUTE_TAG,
key: 'tagPage',
params: {
tag: value.toLowerCase(),
},
});
} else { } else {
const uri = normalizeURI(value); const uri = normalizeURI(value);
navigateToUri(navigation, uri, null, false, null, setPlayerVisible); navigateToUri(navigation, uri);
} }
}; };
_keyboardDidShow = evt => {
this.setState({ keyboardHeight: evt.endCoordinates.height });
};
_keyboardDidHide = () => { _keyboardDidHide = () => {
if (this.textInput) { if (this.textInput) {
this.textInput.blur(); this.textInput.blur();
} }
this.setState({ focused: false, keyboardHeight: 0 }); this.setState({ focused: false });
}; };
handleSubmitEditing = () => { setSelection() {
const { navigation, onSearchSubmitted, setPlayerVisible, updateSearchQuery } = this.props; if (this.textInput) {
if (this.state.inputText) { this.textInput.setNativeProps({ selection: { start: 0, end: 0 } });
let inputText = this.state.inputText, }
inputTextIsUrl = false; }
if (inputText.startsWith('lbry://')) {
const transformedUrl = transformUrl(inputText);
// if it's a URI (lbry://...), open the file page
if (transformedUrl && isURIValid(transformedUrl)) {
inputTextIsUrl = true;
navigateToUri(navigation, transformedUrl, null, false, null, setPlayerVisible);
}
}
// couldn't parse the inputText as a URL for some reason, so do a search instead handleSubmitEditing = () => {
if (!inputTextIsUrl) { const { navigation, onSearchSubmitted, updateSearchQuery } = this.props;
if (this.state.inputText) {
let inputText = this.state.inputText;
if (inputText.startsWith('lbry://') && isURIValid(inputText)) {
// if it's a URI (lbry://...), open the file page
const uri = normalizeURI(inputText);
navigateToUri(navigation, uri);
} else {
updateSearchQuery(inputText); updateSearchQuery(inputText);
// Not a URI, default to a search request // Not a URI, default to a search request
if (onSearchSubmitted) { if (onSearchSubmitted) {
@ -158,58 +129,9 @@ class UriBar extends React.PureComponent {
} }
}; };
handleFocus = () => { onSearchPageBlurred() {
this.setState( this.setState({ currenValueSet: false });
{ }
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 {
@ -223,18 +145,16 @@ class UriBar extends React.PureComponent {
selectedItemCount, selectedItemCount,
selectionMode, selectionMode,
suggestions, suggestions,
showUriBarSuggestions,
value, value,
} = this.props; } = this.props;
if (this.state.currentValue === null) { if (this.state.currentValue === null) {
this.setState({ currentValue: value }); this.setState({ currentValue: value });
} }
let style = [ let style = [uriBarStyle.overlay, belowOverlay ? null : uriBarStyle.overlayElevated];
uriBarStyle.overlay,
belowOverlay ? null : uriBarStyle.overlayElevated, // TODO: Add optional setting to enable URI / search bar suggestions
this.state.focused && showUriBarSuggestions ? uriBarStyle.inFocus : null, /* if (this.state.focused) { style.push(uriBarStyle.inFocus); } */
];
// TODO: selectionModeActions should be dynamically created / specified // TODO: selectionModeActions should be dynamically created / specified
return ( return (
@ -290,7 +210,7 @@ class UriBar extends React.PureComponent {
size={24} size={24}
style={uriBarStyle.drawerMenuButton} style={uriBarStyle.drawerMenuButton}
iconStyle={uriBarStyle.drawerHamburger} iconStyle={uriBarStyle.drawerHamburger}
onPress={this.handleNavigationButtonPress} onPress={() => navigation.openDrawer()}
/> />
)} )}
{!selectionMode && ( {!selectionMode && (
@ -298,11 +218,13 @@ 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}
selection={this.state.selection} onLayout={() => {
placeholder={__('Search movies, music, and more')} this.setSelection();
}}
selectTextOnFocus
placeholder={'Search movies, music, and more'}
underlineColorAndroid={'transparent'} underlineColorAndroid={'transparent'}
numberOfLines={1} numberOfLines={1}
clearButtonMode={'while-editing'} clearButtonMode={'while-editing'}
@ -310,27 +232,29 @@ 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.handleFocus} onFocus={() => this.setState({ focused: true })}
onBlur={this.handleBlur} onBlur={() => {
this.setState({ focused: false });
this.setSelection();
}}
onChangeText={this.handleChangeText} onChangeText={this.handleChangeText}
onSubmitEditing={this.handleSubmitEditing} onSubmitEditing={this.handleSubmitEditing}
/> />
)} )}
{this.state.focused && !this.state.directSearch && (
<View style={uriBarStyle.suggestions}>
<FlatList
style={uriBarStyle.suggestionList}
data={suggestions}
keyboardShouldPersistTaps={'handled'}
keyExtractor={(item, value) => item.value}
renderItem={({ item }) => (
<UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} />
)}
/>
</View>
)}
</View> </View>
{this.state.focused && showUriBarSuggestions && (
<FlatList
style={[
uriBarStyle.suggestions,
{ height: Dimensions.get('window').height - this.state.keyboardHeight - 60 },
]}
data={suggestions}
keyboardShouldPersistTaps={'handled'}
keyExtractor={(item, value) => `${item.value}-${item.type}`}
renderItem={({ item }) => (
<UriBarItem item={item} navigation={navigation} onPress={() => this.handleItemPress(item)} />
)}
/>
)}
</View> </View>
); );
} }

View file

@ -27,22 +27,21 @@ class WalletAddress extends React.PureComponent<Props> {
return ( return (
<View style={walletStyle.card}> <View style={walletStyle.card}>
<Text style={walletStyle.title}>{__('Receive Credits')}</Text> <Text style={walletStyle.title}>Receive Credits</Text>
<Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}> <Text style={[walletStyle.text, walletStyle.bottomMarginMedium]}>
{__('Use this wallet address to receive credits sent by another user (or yourself).')} Use this wallet address to receive credits sent by another user (or yourself).
</Text> </Text>
<Address address={receiveAddress} style={walletStyle.bottomMarginSmall} /> <Address address={receiveAddress} style={walletStyle.bottomMarginSmall} />
<Button <Button
style={[walletStyle.button, walletStyle.bottomMarginLarge]} style={[walletStyle.button, walletStyle.bottomMarginLarge]}
icon={'sync'} icon={'sync'}
text={__('Get new address')} text={'Get new address'}
onPress={getNewAddress} onPress={getNewAddress}
disabled={gettingNewAddress} disabled={gettingNewAddress}
/> />
<Text style={walletStyle.smallText}> <Text style={walletStyle.smallText}>
{__( You can generate a new address at any time, and any previous addresses will continue to work. Using multiple
'You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.' addresses can be helpful for keeping track of incoming payments from multiple sources.
)}
</Text> </Text>
</View> </View>
); );

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