From 492b1601f66d2f02f7df77fbc91a4392e0072494 Mon Sep 17 00:00:00 2001
From: Sean Yesmunt <sean.yesmunt@gmail.com>
Date: Mon, 7 May 2018 00:50:55 -0400
Subject: [PATCH] feature: use internal-apis for subscriptions and add page
 loader

update subscription types

update changelog

Simplify subscriptions sync logic

add claim type

use let over const

change spinner color based on theme

clean up subscriptions
---
 .eslintrc.json                                |   3 +-
 .idea/inspectionProfiles/Project_Default.xml  |   6 +
 .idea/lbry-app.iml                            |  12 +
 .idea/misc.xml                                |   6 +
 .idea/modules.xml                             |   8 +
 .idea/vcs.xml                                 |   6 +
 .idea/workspace.xml                           | 547 ++++++++++++++++++
 CHANGELOG.md                                  |   1 +
 src/renderer/analytics.js                     |  17 -
 src/renderer/component/common/spinner.jsx     |  28 -
 src/renderer/component/fileDetails/view.jsx   |   3 +-
 src/renderer/component/fileList/view.jsx      |   5 +-
 src/renderer/component/page/view.jsx          | 103 +++-
 src/renderer/component/spinner/index.js       |   9 +
 src/renderer/component/spinner/view.jsx       |  36 ++
 .../component/subscribeButton/view.jsx        |   2 +-
 src/renderer/component/uriIndicator/view.jsx  |  19 +-
 .../video/internal/loading-screen.jsx         |   4 +-
 src/renderer/component/video/view.jsx         |   3 +-
 src/renderer/component/walletSendTip/view.jsx |   3 +-
 src/renderer/constants/action_types.js        |  13 +
 src/renderer/constants/themes.js              |   4 +
 src/renderer/page/channel/view.jsx            |  12 +-
 src/renderer/page/file/view.jsx               |  12 +-
 src/renderer/page/show/view.jsx               |   7 +-
 src/renderer/page/subscriptions/index.js      |  20 +-
 src/renderer/page/subscriptions/view.jsx      | 109 ++--
 src/renderer/redux/actions/content.js         |   1 +
 src/renderer/redux/actions/subscriptions.js   | 225 +++++--
 src/renderer/redux/reducers/subscriptions.js  |  41 +-
 src/renderer/redux/selectors/subscriptions.js |  29 +-
 src/renderer/scss/_gui.scss                   |   3 +
 src/renderer/scss/component/_card.scss        |  30 +-
 src/renderer/scss/component/_spinner.scss     |   7 +-
 src/renderer/store.js                         |   1 +
 src/renderer/types/claim.js                   |  30 +
 src/renderer/types/subscription.js            |   7 +
 src/renderer/util/dom.js                      |  12 +
 38 files changed, 1090 insertions(+), 294 deletions(-)
 create mode 100644 .idea/inspectionProfiles/Project_Default.xml
 create mode 100644 .idea/lbry-app.iml
 create mode 100644 .idea/misc.xml
 create mode 100644 .idea/modules.xml
 create mode 100644 .idea/vcs.xml
 create mode 100644 .idea/workspace.xml
 create mode 100644 src/renderer/component/spinner/index.js
 create mode 100644 src/renderer/component/spinner/view.jsx
 create mode 100644 src/renderer/constants/themes.js
 create mode 100644 src/renderer/types/claim.js
 create mode 100644 src/renderer/types/subscription.js
 create mode 100644 src/renderer/util/dom.js

diff --git a/.eslintrc.json b/.eslintrc.json
index 56319633d..eb8c5c0fa 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -34,6 +34,7 @@
       "singleQuote": true
     }],
     "func-names": ["warn", "as-needed"],
-    "jsx-a11y/label-has-for": 0
+    "jsx-a11y/label-has-for": 0,
+    "import/prefer-default-export": 0
   }
 }
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 000000000..c6cc8c819
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="ERROR" enabled_by_default="true" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/lbry-app.iml b/.idea/lbry-app.iml
new file mode 100644
index 000000000..24643cc37
--- /dev/null
+++ b/.idea/lbry-app.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 000000000..3668dc8ca
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="FLOW" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..b37b46a86
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/lbry-app.iml" filepath="$PROJECT_DIR$/.idea/lbry-app.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..94a25f7f4
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 000000000..a51b0ec12
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,547 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <list default="true" id="061e089e-71dc-4d6b-b27c-9c614b097257" name="Default" comment="">
+      <change type="NEW" beforePath="" afterPath="$PROJECT_DIR$/src/renderer/types/claim.js" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/fileDetails/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/fileDetails/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/uriIndicator/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/uriIndicator/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/video/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/video/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/component/walletSendTip/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/component/walletSendTip/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/channel/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/channel/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/file/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/file/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/show/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/show/view.jsx" />
+      <change type="MODIFICATION" beforePath="$PROJECT_DIR$/src/renderer/page/subscriptions/view.jsx" afterPath="$PROJECT_DIR$/src/renderer/page/subscriptions/view.jsx" />
+    </list>
+    <ignored path="$PROJECT_DIR$/.tmp/" />
+    <ignored path="$PROJECT_DIR$/temp/" />
+    <ignored path="$PROJECT_DIR$/tmp/" />
+    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
+    <option name="TRACKING_ENABLED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="FileEditorManager">
+    <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
+      <file leaf-file-name="view.jsx" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="0">
+              <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+              <folding>
+                <element signature="e#0#26#0" expanded="true" />
+                <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="index.js" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="216">
+              <caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
+              <folding>
+                <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="package.json" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/package.json">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="1332">
+              <caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
+              <folding>
+                <marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
+              </folding>
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="join.js" pinned="false" current-in-tab="false">
+        <entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="144">
+              <caret line="8" column="11" lean-forward="false" selection-start-line="8" selection-start-column="11" selection-end-line="8" selection-end-column="11" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+      <file leaf-file-name="view.jsx" pinned="false" current-in-tab="true">
+        <entry file="file://$PROJECT_DIR$/src/renderer/page/file/view.jsx">
+          <provider selected="true" editor-type-id="text-editor">
+            <state relative-caret-position="287">
+              <caret line="18" column="15" lean-forward="false" selection-start-line="18" selection-start-column="14" selection-end-line="18" selection-end-column="15" />
+              <folding />
+            </state>
+          </provider>
+        </entry>
+      </file>
+    </leaf>
+  </component>
+  <component name="FindInProjectRecents">
+    <findStrings>
+      <find>TYPE_FEATURED_DOWNLOAD</find>
+      <find>TYPE_FEATURED_DOW</find>
+      <find>doOpen</find>
+      <find>doNotify</find>
+      <find>settings</find>
+    </findStrings>
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+  </component>
+  <component name="IdeDocumentHistory">
+    <option name="CHANGED_PATHS">
+      <list>
+        <option value="$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js" />
+        <option value="$PROJECT_DIR$/package.json" />
+        <option value="$PROJECT_DIR$/README.md" />
+        <option value="$PROJECT_DIR$/src/renderer/page/file/view.jsx" />
+      </list>
+    </option>
+  </component>
+  <component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
+  <component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER">
+    <package-json value="$PROJECT_DIR$/package.json" />
+  </component>
+  <component name="JsFlowSettings">
+    <service-enabled>true</service-enabled>
+    <exe-path />
+    <annotation-enable>false</annotation-enable>
+    <other-services-enabled>true</other-services-enabled>
+    <auto-save>true</auto-save>
+  </component>
+  <component name="JsGulpfileManager">
+    <detection-done>true</detection-done>
+    <sorting>DEFINITION_ORDER</sorting>
+  </component>
+  <component name="NodeModulesDirectoryManager">
+    <handled-path value="$PROJECT_DIR$/dist/linux-unpacked/resources/app.asar.unpacked/node_modules" />
+    <handled-path value="$PROJECT_DIR$/node_modules" />
+  </component>
+  <component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
+  <component name="ProjectFrameBounds" extendedState="6">
+    <option name="y" value="24" />
+    <option name="width" value="1920" />
+    <option name="height" value="1056" />
+  </component>
+  <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
+  <component name="ProjectView">
+    <navigator currentView="ProjectPane" proportions="" version="1">
+      <flattenPackages />
+      <showMembers />
+      <showModules />
+      <showLibraryContents />
+      <hideEmptyPackages />
+      <abbreviatePackageNames />
+      <autoscrollToSource />
+      <autoscrollFromSource />
+      <sortByType />
+      <manualOrder />
+      <foldersAlwaysOnTop value="true" />
+    </navigator>
+    <panes>
+      <pane id="Scope" />
+      <pane id="ProjectPane">
+        <subPane>
+          <expand>
+            <path>
+              <item name="lbry-app" type="b2602c69:ProjectViewProjectNode" />
+              <item name="lbry-app" type="2a2b976b:PhpTreeStructureProvider$1" />
+            </path>
+          </expand>
+          <select />
+        </subPane>
+      </pane>
+      <pane id="Scratches" />
+    </panes>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="nodejs_interpreter_path" value="/usr/local/bin/node" />
+    <property name="HbShouldOpenHtmlAsHb" value="" />
+    <property name="node.js.path.for.package.eslint" value="project" />
+    <property name="node.js.detected.package.eslint" value="true" />
+    <property name="node.js.selected.package.eslint" value="$PROJECT_DIR$/node_modules/eslint" />
+    <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+  </component>
+  <component name="RunDashboard">
+    <option name="ruleStates">
+      <list>
+        <RuleState>
+          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
+        </RuleState>
+        <RuleState>
+          <option name="name" value="StatusDashboardGroupingRule" />
+        </RuleState>
+      </list>
+    </option>
+  </component>
+  <component name="ShelveChangesManager" show_recycled="false">
+    <option name="remove_strategy" value="false" />
+  </component>
+  <component name="SvnConfiguration">
+    <configuration />
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="061e089e-71dc-4d6b-b27c-9c614b097257" name="Default" comment="" />
+      <created>1522354295512</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1522354295512</updated>
+      <workItem from="1522354299246" duration="1866000" />
+      <workItem from="1522643117648" duration="2000" />
+      <workItem from="1524064618567" duration="2674000" />
+      <workItem from="1525381677126" duration="197000" />
+      <workItem from="1525382363305" duration="597000" />
+      <workItem from="1525461458836" duration="70000" />
+      <workItem from="1526067492327" duration="897000" />
+      <workItem from="1526397339713" duration="27000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TimeTrackingManager">
+    <option name="totallyTimeSpent" value="6330000" />
+  </component>
+  <component name="ToolWindowManager">
+    <frame x="0" y="24" width="1920" height="1055" extended-state="6" />
+    <editor active="true" />
+    <layout>
+      <window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
+      <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
+      <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
+      <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+      <window_info id="npm" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
+      <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
+      <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+      <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
+      <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
+      <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+      <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
+      <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
+      <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
+      <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
+      <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+      <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
+      <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
+    </layout>
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="1" />
+  </component>
+  <component name="VcsContentAnnotationSettings">
+    <option name="myLimit" value="2678400000" />
+  </component>
+  <component name="XDebuggerManager">
+    <breakpoint-manager />
+    <watches-manager />
+  </component>
+  <component name="editorHistoryManager">
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="216">
+          <caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/package.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1332">
+          <caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
+          <folding>
+            <marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="144">
+          <caret line="8" column="11" lean-forward="false" selection-start-line="8" selection-start-column="11" selection-end-line="8" selection-end-column="11" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/package.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1332">
+          <caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
+          <folding>
+            <marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="216">
+          <caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="216">
+          <caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/package.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="2322">
+          <caret line="129" column="38" lean-forward="false" selection-start-line="129" selection-start-column="38" selection-end-line="129" selection-end-column="38" />
+          <folding>
+            <marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="144">
+          <caret line="19" column="68" lean-forward="true" selection-start-line="19" selection-start-column="68" selection-end-line="19" selection-end-column="68" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/page/rewards/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-122">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/redux/reducers/rewards.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-28">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/redux/actions/rewards.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-832">
+          <caret line="13" column="61" lean-forward="false" selection-start-line="13" selection-start-column="36" selection-end-line="13" selection-end-column="61" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/rewards.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="324">
+          <caret line="18" column="0" lean-forward="false" selection-start-line="18" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="216">
+          <caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/package.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="693">
+          <caret line="129" column="38" lean-forward="false" selection-start-line="129" selection-start-column="38" selection-end-line="129" selection-end-column="38" />
+          <folding>
+            <marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="144">
+          <caret line="19" column="68" lean-forward="true" selection-start-line="19" selection-start-column="68" selection-end-line="19" selection-end-column="68" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/page/rewards/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-122">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/redux/reducers/rewards.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-28">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/redux/actions/rewards.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="-832">
+          <caret line="13" column="61" lean-forward="false" selection-start-line="13" selection-start-column="36" selection-end-line="13" selection-end-column="61" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/rewards.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="324">
+          <caret line="18" column="0" lean-forward="false" selection-start-line="18" selection-start-column="0" selection-end-line="18" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="0">
+          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
+          <folding>
+            <element signature="e#0#26#0" expanded="true" />
+            <marker date="1526068149928" expanded="true" signature="5366:5471" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/modal/modalRouter/index.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="216">
+          <caret line="12" column="0" lean-forward="false" selection-start-line="12" selection-start-column="0" selection-end-line="12" selection-end-column="0" />
+          <folding>
+            <marker date="1525123536618" expanded="true" signature="1172:1177" ph="{...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/package.json">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="1332">
+          <caret line="74" column="0" lean-forward="false" selection-start-line="74" selection-start-column="0" selection-end-line="74" selection-end-column="0" />
+          <folding>
+            <marker date="1526397284183" expanded="true" signature="2668:3140" ph="{&quot;axios&quot;: &quot;^0.18.0&quot;...}" />
+          </folding>
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/CONTRIBUTING.md">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="540">
+          <caret line="168" column="0" lean-forward="true" selection-start-line="168" selection-start-column="0" selection-end-line="168" selection-end-column="0" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/README.md">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="846">
+          <caret line="47" column="81" lean-forward="true" selection-start-line="47" selection-start-column="81" selection-end-line="47" selection-end-column="81" />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/node_modules/bluebird/js/release/join.js">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="144">
+          <caret line="8" column="11" lean-forward="false" selection-start-line="8" selection-start-column="11" selection-end-line="8" selection-end-column="11" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+    <entry file="file://$PROJECT_DIR$/src/renderer/page/file/view.jsx">
+      <provider selected="true" editor-type-id="text-editor">
+        <state relative-caret-position="287">
+          <caret line="18" column="15" lean-forward="false" selection-start-line="18" selection-start-column="14" selection-end-line="18" selection-end-column="15" />
+          <folding />
+        </state>
+      </provider>
+    </entry>
+  </component>
+</project>
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d31dbbc93..f00467470 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
 ### Changed
    * Add flair to snackbar ([#1313](https://github.com/lbryio/lbry-app/pull/1313))
    * Made font in price badge larger ([#1420](https://github.com/lbryio/lbry-app/pull/1420))
+   * Store subscriptions in internal database ([#1424](https://github.com/lbryio/lbry-app/pull/1424))
 
 ### Fixed
    * Fix content-type not shown correctly in file description ([#863](https://github.com/lbryio/lbry-app/pull/863))
diff --git a/src/renderer/analytics.js b/src/renderer/analytics.js
index 2a03b7428..a4fd24258 100644
--- a/src/renderer/analytics.js
+++ b/src/renderer/analytics.js
@@ -2,7 +2,6 @@
 import mixpanel from 'mixpanel-browser';
 import Lbryio from 'lbryio';
 import isDev from 'electron-is-dev';
-import type { Subscription } from 'redux/reducers/subscriptions';
 
 if (isDev) {
   mixpanel.init('691723e855cabb9d27a7a79002216967');
@@ -15,8 +14,6 @@ type Analytics = {
   setUser: Object => void,
   toggle: (boolean, ?boolean) => void,
   apiLogView: (string, string, string) => void,
-  apiLogSubscribe: Subscription => void,
-  apiLogUnsubscribe: Subscription => void,
 };
 
 let analyticsEnabled: boolean = false;
@@ -56,20 +53,6 @@ const analytics: Analytics = {
       }).catch(() => {});
     }
   },
-  apiLogSubscribe: (subscription: Subscription): void => {
-    if (analyticsEnabled) {
-      Lbryio.call('subscription', 'new', {
-        subscription,
-      }).catch(() => {});
-    }
-  },
-  apiLogUnsubscribe: (subscription: Subscription): void => {
-    if (analyticsEnabled) {
-      Lbryio.call('subscription', 'delete', {
-        subscription,
-      }).catch(() => {});
-    }
-  },
 };
 
 export default analytics;
diff --git a/src/renderer/component/common/spinner.jsx b/src/renderer/component/common/spinner.jsx
index 7ba514607..e69de29bb 100644
--- a/src/renderer/component/common/spinner.jsx
+++ b/src/renderer/component/common/spinner.jsx
@@ -1,28 +0,0 @@
-// @flow
-import * as React from 'react';
-import classnames from 'classnames';
-
-type Props = {
-  dark?: boolean,
-};
-
-class Spinner extends React.Component<Props> {
-  static defaultProps = {
-    dark: false,
-  };
-
-  render() {
-    const { dark } = this.props;
-    return (
-      <div className={classnames('spinner', { 'spinner--dark': dark })}>
-        <div className="rect rect1" />
-        <div className="rect rect2" />
-        <div className="rect rect3" />
-        <div className="rect rect4" />
-        <div className="rect rect5" />
-      </div>
-    );
-  }
-}
-
-export default Spinner;
diff --git a/src/renderer/component/fileDetails/view.jsx b/src/renderer/component/fileDetails/view.jsx
index e49af3417..c793d25ef 100644
--- a/src/renderer/component/fileDetails/view.jsx
+++ b/src/renderer/component/fileDetails/view.jsx
@@ -3,9 +3,10 @@ import * as React from 'react';
 import ReactMarkdown from 'react-markdown';
 import Button from 'component/button';
 import path from 'path';
+import type { Claim } from 'types/claim';
 
 type Props = {
-  claim: {},
+  claim: Claim,
   fileInfo: {
     download_path: string,
   },
diff --git a/src/renderer/component/fileList/view.jsx b/src/renderer/component/fileList/view.jsx
index ffe8ecbff..8bc26d362 100644
--- a/src/renderer/component/fileList/view.jsx
+++ b/src/renderer/component/fileList/view.jsx
@@ -53,7 +53,6 @@ class FileList extends React.PureComponent<Props, State> {
               if (fileInfo1.pending) {
                 return -1;
               }
-
               const height1 = this.props.claimsById[fileInfo1.claim_id]
                 ? this.props.claimsById[fileInfo1.claim_id].height
                 : 0;
@@ -145,6 +144,10 @@ class FileList extends React.PureComponent<Props, State> {
     const { sortBy } = this.state;
     const content = [];
 
+    if (!fileInfos) {
+      return null;
+    }
+
     this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => {
       const {
         channel_name: channelName,
diff --git a/src/renderer/component/page/view.jsx b/src/renderer/component/page/view.jsx
index 40ec8564b..9af308b79 100644
--- a/src/renderer/component/page/view.jsx
+++ b/src/renderer/component/page/view.jsx
@@ -1,33 +1,98 @@
 // @flow
 import * as React from 'react';
 import classnames from 'classnames';
+import Spinner from 'component/spinner';
+import { isShowingChildren } from 'util/dom';
+
+// time in ms to wait to show loading spinner
+const LOADER_TIMEOUT = 1500;
 
 type Props = {
-  children: React.Node,
+  children: React.Node | Array<React.Node>,
   pageTitle: ?string,
   noPadding: ?boolean,
   extraPadding: ?boolean,
   notContained: ?boolean, // No max-width, but keep the padding
+  loading: ?boolean,
 };
 
-const Page = (props: Props) => {
-  const { pageTitle, children, noPadding, extraPadding, notContained } = props;
-  return (
-    <main
-      className={classnames('main', {
-        'main--contained': !notContained && !noPadding && !extraPadding,
-        'main--no-padding': noPadding,
-        'main--extra-padding': extraPadding,
-      })}
-    >
-      {pageTitle && (
-        <div className="page__header">
-          {pageTitle && <h1 className="page__title">{pageTitle}</h1>}
-        </div>
-      )}
-      {children}
-    </main>
-  );
+type State = {
+  showLoader: ?boolean,
 };
 
+class Page extends React.PureComponent<Props, State> {
+  static getDerivedStateFromProps(nextProps: Props, prevState: State) {
+    const { children } = nextProps;
+    const { showLoader } = prevState;
+
+    // If we aren't showing the loader, don't bother updating
+    if (!showLoader) {
+      return null;
+    }
+
+    if (isShowingChildren(children)) {
+      return {
+        showLoader: false,
+      };
+    }
+    return null;
+  }
+
+  constructor() {
+    super();
+
+    this.state = {
+      showLoader: false,
+    };
+
+    this.loaderTimeout = null;
+  }
+
+  componentDidMount() {
+    const { children } = this.props;
+
+    if (!isShowingChildren(children))
+      this.loaderTimeout = setTimeout(() => {
+        this.setState({ showLoader: true });
+      }, LOADER_TIMEOUT);
+  }
+
+  componentWillUnmount() {
+    this.loaderTimeout = null;
+  }
+
+  loaderTimeout: ?TimeoutID;
+
+  render() {
+    const { pageTitle, children, noPadding, extraPadding, notContained, loading } = this.props;
+    const { showLoader } = this.state;
+
+    // We don't want to show the loading spinner right away if it will only flash on the
+    // screen for a short time, wait until we know it will be loading for a bit before showing it
+    const shouldShowLoader = !isShowingChildren(children) && showLoader;
+
+    return (
+      <main
+        className={classnames('main', {
+          'main--contained': !notContained && !noPadding && !extraPadding,
+          'main--no-padding': noPadding,
+          'main--extra-padding': extraPadding,
+        })}
+      >
+        {pageTitle && (
+          <div className="page__header">
+            {pageTitle && <h1 className="page__title">{pageTitle}</h1>}
+          </div>
+        )}
+        {!loading && children}
+        {shouldShowLoader && (
+          <div className="page__empty">
+            <Spinner />
+          </div>
+        )}
+      </main>
+    );
+  }
+}
+
 export default Page;
diff --git a/src/renderer/component/spinner/index.js b/src/renderer/component/spinner/index.js
new file mode 100644
index 000000000..6589e05da
--- /dev/null
+++ b/src/renderer/component/spinner/index.js
@@ -0,0 +1,9 @@
+import { connect } from 'react-redux';
+import { selectTheme } from 'redux/selectors/settings';
+import Spinner from './view';
+
+const mapStateToProps = state => ({
+  theme: selectTheme(state),
+});
+
+export default connect(mapStateToProps, null)(Spinner);
diff --git a/src/renderer/component/spinner/view.jsx b/src/renderer/component/spinner/view.jsx
new file mode 100644
index 000000000..bb44ee6f7
--- /dev/null
+++ b/src/renderer/component/spinner/view.jsx
@@ -0,0 +1,36 @@
+// @flow
+import * as React from 'react';
+import classnames from 'classnames';
+import { DARK_THEME, LIGHT_THEME } from 'constants/themes';
+
+type Props = {
+  dark?: boolean, // always a dark spinner
+  light?: boolean, // always a light spinner
+  theme: string,
+};
+
+const Spinner = (props: Props) => {
+  const { dark, light, theme } = props;
+
+  return (
+    <div
+      className={classnames('spinner', {
+        'spinner--dark': !light && (dark || theme === LIGHT_THEME),
+        'spinner--light': !dark && (light || theme === DARK_THEME),
+      })}
+    >
+      <div className="rect rect1" />
+      <div className="rect rect2" />
+      <div className="rect rect3" />
+      <div className="rect rect4" />
+      <div className="rect rect5" />
+    </div>
+  );
+};
+
+Spinner.defaultProps = {
+  dark: false,
+  light: false,
+};
+
+export default Spinner;
diff --git a/src/renderer/component/subscribeButton/view.jsx b/src/renderer/component/subscribeButton/view.jsx
index 1a341447f..52516dfb8 100644
--- a/src/renderer/component/subscribeButton/view.jsx
+++ b/src/renderer/component/subscribeButton/view.jsx
@@ -3,7 +3,7 @@ import React from 'react';
 import { MODALS } from 'lbry-redux';
 import * as icons from 'constants/icons';
 import Button from 'component/button';
-import type { Subscription } from 'redux/reducers/subscriptions';
+import type { Subscription } from 'types/subscription';
 
 type SubscribtionArgs = {
   channelName: string,
diff --git a/src/renderer/component/uriIndicator/view.jsx b/src/renderer/component/uriIndicator/view.jsx
index 499b11fba..98c249932 100644
--- a/src/renderer/component/uriIndicator/view.jsx
+++ b/src/renderer/component/uriIndicator/view.jsx
@@ -3,21 +3,18 @@ import React from 'react';
 import Button from 'component/button';
 import { buildURI } from 'lbry-redux';
 import classnames from 'classnames';
-// import Icon from 'component/common/icon';
+import type { Claim } from 'types/claim';
 
 type Props = {
   isResolvingUri: boolean,
-  resolveUri: string => void,
-  claim: {
-    channel_name: string,
-    has_signature: boolean,
-    signature_is_valid: boolean,
-    value: {
-      publisherSignature: { certificateId: string },
-    },
-  },
-  uri: string,
+  claim: Claim,
   link: ?boolean,
+  // Lint thinks we aren't using these, even though we are.
+  // Possibly because the resolve function is an arrow function that is passed in props?
+  /* eslint-disable react/no-unused-prop-types */
+  resolveUri: string => void,
+  uri: string,
+  /* eslint-enable react/no-unused-prop-types */
 };
 
 class UriIndicator extends React.PureComponent<Props> {
diff --git a/src/renderer/component/video/internal/loading-screen.jsx b/src/renderer/component/video/internal/loading-screen.jsx
index ccada9d41..f313b412d 100644
--- a/src/renderer/component/video/internal/loading-screen.jsx
+++ b/src/renderer/component/video/internal/loading-screen.jsx
@@ -1,6 +1,6 @@
 // @flow
 import React from 'react';
-import Spinner from 'component/common/spinner';
+import Spinner from 'component/spinner';
 
 type Props = {
   spinner: boolean,
@@ -16,7 +16,7 @@ class LoadingScreen extends React.PureComponent<Props> {
     const { status, spinner } = this.props;
     return (
       <div className="content__loading">
-        {spinner && <Spinner />}
+        {spinner && <Spinner light />}
 
         <span className="content__loading-text">{status}</span>
       </div>
diff --git a/src/renderer/component/video/view.jsx b/src/renderer/component/video/view.jsx
index c1663c5e3..761a19e10 100644
--- a/src/renderer/component/video/view.jsx
+++ b/src/renderer/component/video/view.jsx
@@ -2,6 +2,7 @@
 import React from 'react';
 import { Lbry } from 'lbry-redux';
 import classnames from 'classnames';
+import type { Claim } from 'types/claim';
 import VideoPlayer from './internal/player';
 import VideoPlayButton from './internal/play-button';
 import LoadingScreen from './internal/loading-screen';
@@ -26,7 +27,7 @@ type Props = {
   contentType: string,
   changeVolume: number => void,
   volume: number,
-  claim: {},
+  claim: Claim,
   uri: string,
   doPlay: () => void,
   doPause: () => void,
diff --git a/src/renderer/component/walletSendTip/view.jsx b/src/renderer/component/walletSendTip/view.jsx
index 9af0da866..a8d10a8a5 100644
--- a/src/renderer/component/walletSendTip/view.jsx
+++ b/src/renderer/component/walletSendTip/view.jsx
@@ -3,11 +3,12 @@ import React from 'react';
 import Button from 'component/button';
 import { FormField } from 'component/common/form';
 import UriIndicator from 'component/uriIndicator';
+import type { Claim } from 'types/claim';
 
 type Props = {
   uri: string,
   title: string,
-  claim: { claim_id: string },
+  claim: Claim,
   errorMessage: string,
   isPending: boolean,
   sendSupport: (number, string, string) => void,
diff --git a/src/renderer/constants/action_types.js b/src/renderer/constants/action_types.js
index 27678804b..3ffe23e6e 100644
--- a/src/renderer/constants/action_types.js
+++ b/src/renderer/constants/action_types.js
@@ -1,3 +1,13 @@
+/*
+  Constants for redux actions
+  All names should be in present tense
+  ex:
+  XXX_START
+  XXX_SUCCESS
+  XXX_FAIL
+  XXX_COMPLETE // if there is no fail case
+*/
+
 export const WINDOW_FOCUSED = 'WINDOW_FOCUSED';
 export const DAEMON_READY = 'DAEMON_READY';
 export const DAEMON_VERSION_MATCH = 'DAEMON_VERSION_MATCH';
@@ -166,6 +176,9 @@ export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
 export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
 export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
 export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
+export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
+export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
+export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
 
 // Video controls
 export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
diff --git a/src/renderer/constants/themes.js b/src/renderer/constants/themes.js
new file mode 100644
index 000000000..0b9cc53b6
--- /dev/null
+++ b/src/renderer/constants/themes.js
@@ -0,0 +1,4 @@
+// css theme values
+// saved in settings and found at /static/themes/{theme}.css
+export const DARK_THEME = 'dark';
+export const LIGHT_THEME = 'light';
diff --git a/src/renderer/page/channel/view.jsx b/src/renderer/page/channel/view.jsx
index 59a5b5531..693cb3c89 100644
--- a/src/renderer/page/channel/view.jsx
+++ b/src/renderer/page/channel/view.jsx
@@ -6,6 +6,7 @@ import ReactPaginate from 'react-paginate';
 import SubscribeButton from 'component/subscribeButton';
 import Page from 'component/page';
 import FileList from 'component/fileList';
+import type { Claim } from 'types/claim';
 
 type Props = {
   uri: string,
@@ -13,10 +14,7 @@ type Props = {
   totalPages: number,
   fetching: boolean,
   params: { page: number },
-  claim: {
-    name: string,
-    claim_id: string,
-  },
+  claim: Claim,
   claimsInChannel: Array<{}>,
   fetchClaims: (string, number) => void,
   fetchClaimCount: string => void,
@@ -58,8 +56,8 @@ class ChannelPage extends React.PureComponent<Props> {
   }
 
   render() {
-    const { fetching, claimsInChannel, claim, uri, page, totalPages } = this.props;
-    const { name } = claim;
+    const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
+    const { name, permanent_url: permanentUrl } = claim;
 
     let contentList;
     if (fetching) {
@@ -78,7 +76,7 @@ class ChannelPage extends React.PureComponent<Props> {
         <section className="card__channel-info card__channel-info--large">
           <h1>{name}</h1>
           <div className="card__actions card__actions--no-margin">
-            <SubscribeButton uri={uri} channelName={name} />
+            <SubscribeButton uri={permanentUrl} channelName={name} />
           </div>
         </section>
         <section>{contentList}</section>
diff --git a/src/renderer/page/file/view.jsx b/src/renderer/page/file/view.jsx
index 40cc010a2..bf7c34846 100644
--- a/src/renderer/page/file/view.jsx
+++ b/src/renderer/page/file/view.jsx
@@ -16,18 +16,10 @@ import SubscribeButton from 'component/subscribeButton';
 import Page from 'component/page';
 import player from 'render-media';
 import * as settings from 'constants/settings';
+import type { Claim } from 'types/claim';
 
 type Props = {
-  claim: {
-    claim_id: string,
-    height: number,
-    channel_name: string,
-    value: {
-      publisherSignature: ?{
-        certificateId: ?string,
-      },
-    },
-  },
+  claim: Claim,
   fileInfo: {},
   metadata: {
     title: string,
diff --git a/src/renderer/page/show/view.jsx b/src/renderer/page/show/view.jsx
index aa6e042ad..afa753be6 100644
--- a/src/renderer/page/show/view.jsx
+++ b/src/renderer/page/show/view.jsx
@@ -5,16 +5,13 @@ import ChannelPage from 'page/channel';
 import FilePage from 'page/file';
 import Page from 'component/page';
 import Button from 'component/button';
+import type { Claim } from 'types/claim';
 
 type Props = {
   isResolvingUri: boolean,
   resolveUri: string => void,
   uri: string,
-  claim: {
-    name: string,
-    txid: string,
-    nout: number,
-  },
+  claim: Claim,
   blackListedOutpoints: Array<{
     txid: string,
     nout: number,
diff --git a/src/renderer/page/subscriptions/index.js b/src/renderer/page/subscriptions/index.js
index 4d1ce6409..433adc80c 100644
--- a/src/renderer/page/subscriptions/index.js
+++ b/src/renderer/page/subscriptions/index.js
@@ -1,27 +1,25 @@
-import React from 'react';
 import { connect } from 'react-redux';
 import {
-  selectSubscriptionsFromClaims,
+  selectSubscriptionClaims,
   selectSubscriptions,
-  selectHasFetchedSubscriptions,
+  selectSubscriptionsBeingFetched,
+  selectIsFetchingSubscriptions,
   selectNotifications,
 } from 'redux/selectors/subscriptions';
 import { doFetchClaimsByChannel } from 'redux/actions/content';
-import {
-  setHasFetchedSubscriptions,
-  setSubscriptionNotifications,
-} from 'redux/actions/subscriptions';
+import { setSubscriptionNotifications, doFetchMySubscriptions } from 'redux/actions/subscriptions';
 import SubscriptionsPage from './view';
 
 const select = state => ({
-  hasFetchedSubscriptions: state.subscriptions.hasFetchedSubscriptions,
-  savedSubscriptions: selectSubscriptions(state),
-  subscriptions: selectSubscriptionsFromClaims(state),
+  isFetchingSubscriptions: selectIsFetchingSubscriptions(state),
+  subscriptionsBeingFetched: selectSubscriptionsBeingFetched(state),
+  subscriptions: selectSubscriptions(state),
+  subscriptionClaims: selectSubscriptionClaims(state),
   notifications: selectNotifications(state),
 });
 
 export default connect(select, {
   doFetchClaimsByChannel,
-  setHasFetchedSubscriptions,
   setSubscriptionNotifications,
+  doFetchMySubscriptions,
 })(SubscriptionsPage);
diff --git a/src/renderer/page/subscriptions/view.jsx b/src/renderer/page/subscriptions/view.jsx
index 27f3fac45..1ea53e306 100644
--- a/src/renderer/page/subscriptions/view.jsx
+++ b/src/renderer/page/subscriptions/view.jsx
@@ -1,39 +1,28 @@
 // @flow
 import React from 'react';
 import Page from 'component/page';
-import CategoryList from 'component/common/category-list';
-import type { Subscription } from 'redux/reducers/subscriptions';
+import type { Subscription } from 'types/subscription';
 import * as NOTIFICATION_TYPES from 'constants/notification_types';
 import Button from 'component/button';
-
-type SavedSubscriptions = Array<Subscription>;
+import FileList from 'component/fileList';
+import type { Claim } from 'types/claim';
 
 type Props = {
-  doFetchClaimsByChannel: (string, number) => any,
-  savedSubscriptions: SavedSubscriptions,
-  // TODO build out claim types
-  subscriptions: Array<any>,
-  setHasFetchedSubscriptions: () => void,
-  hasFetchedSubscriptions: boolean,
+  doFetchClaimsByChannel: (string, number) => void,
+  doFetchMySubscriptions: () => void,
+  setSubscriptionNotifications: ({}) => void,
+  subscriptions: Array<Subscription>,
+  isFetchingSubscriptions: boolean,
+  subscriptionClaims: Array<{ uri: string, claims: Array<Claim> }>,
+  subscriptionsBeingFetched: {},
+  notifications: {},
 };
 
 export default class extends React.PureComponent<Props> {
-  // setHasFetchedSubscriptions is a terrible hack
-  // it allows the subscriptions to load correctly when refresing on the subscriptions page
-  // currently the page is rendered before the state is rehyrdated
-  // that causes this component to be rendered with zero savedSubscriptions
-  // we need to wait until persist/REHYDRATE has fired before rendering the page
   componentDidMount() {
-    const {
-      savedSubscriptions,
-      setHasFetchedSubscriptions,
-      notifications,
-      setSubscriptionNotifications,
-    } = this.props;
-    if (savedSubscriptions.length) {
-      this.fetchSubscriptions(savedSubscriptions);
-      setHasFetchedSubscriptions();
-    }
+    const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
+    doFetchMySubscriptions();
+
     const newNotifications = {};
     Object.keys(notifications).forEach(cur => {
       if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
@@ -43,40 +32,37 @@ export default class extends React.PureComponent<Props> {
     setSubscriptionNotifications(newNotifications);
   }
 
-  componentWillReceiveProps(props: Props) {
-    const { savedSubscriptions, hasFetchedSubscriptions, setHasFetchedSubscriptions } = props;
+  componentDidUpdate() {
+    const {
+      subscriptions,
+      subscriptionClaims,
+      doFetchClaimsByChannel,
+      subscriptionsBeingFetched,
+    } = this.props;
 
-    if (!hasFetchedSubscriptions && savedSubscriptions.length) {
-      this.fetchSubscriptions(savedSubscriptions);
-      setHasFetchedSubscriptions();
-    }
-  }
+    const subscriptionClaimMap = {};
+    subscriptionClaims.forEach(claim => {
+      subscriptionClaimMap[claim.uri] = 1;
+    });
 
-  fetchSubscriptions(savedSubscriptions: SavedSubscriptions) {
-    const { doFetchClaimsByChannel } = this.props;
-    if (savedSubscriptions.length) {
-      // can this use batchActions?
-      savedSubscriptions.forEach(sub => {
+    subscriptions.forEach(sub => {
+      if (!subscriptionClaimMap[sub.uri] && !subscriptionsBeingFetched[sub.uri]) {
         doFetchClaimsByChannel(sub.uri, 1);
-      });
-    }
+      }
+    });
   }
 
   render() {
-    const { subscriptions, savedSubscriptions } = this.props;
+    const { subscriptions, subscriptionClaims, isFetchingSubscriptions } = this.props;
 
-    // TODO: if you are subscribed to an empty channel, this will always be true (but it should not be)
-    const someClaimsNotLoaded = Boolean(
-      subscriptions.find(subscription => !subscription.claims.length)
-    );
-
-    const fetchingSubscriptions =
-      !!savedSubscriptions.length &&
-      (subscriptions.length !== savedSubscriptions.length || someClaimsNotLoaded);
+    let claimList = [];
+    subscriptionClaims.forEach(claimData => {
+      claimList = claimList.concat(claimData.claims);
+    });
 
     return (
-      <Page noPadding isLoading={fetchingSubscriptions}>
-        {!savedSubscriptions.length && (
+      <Page notContained loading={isFetchingSubscriptions}>
+        {!subscriptions.length && (
           <div className="page__empty">
             {__("It looks like you aren't subscribed to any channels yet.")}
             <div className="card__actions card__actions--center">
@@ -84,28 +70,7 @@ export default class extends React.PureComponent<Props> {
             </div>
           </div>
         )}
-        {!!savedSubscriptions.length && (
-          <div>
-            {!!subscriptions.length &&
-              subscriptions.map(subscription => {
-                if (!subscription.claims.length) {
-                  // will need to update when you can subscribe to empty channels
-                  // for now this prevents issues with FeaturedCategory being rendered
-                  // before the names (claim uris) are populated
-                  return '';
-                }
-
-                return (
-                  <CategoryList
-                    key={subscription.channelName}
-                    categoryLink={subscription.uri}
-                    category={subscription.channelName}
-                    names={subscription.claims}
-                  />
-                );
-              })}
-          </div>
-        )}
+        {!!claimList.length && <FileList hideFilter sortByHeight fileInfos={claimList} />}
       </Page>
     );
   }
diff --git a/src/renderer/redux/actions/content.js b/src/renderer/redux/actions/content.js
index 1ae851565..8dae86f8f 100644
--- a/src/renderer/redux/actions/content.js
+++ b/src/renderer/redux/actions/content.js
@@ -138,6 +138,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
                 : acc,
             0
           );
+
           const notif = new window.Notification(notifications[uri].subscription.channelName, {
             body: `Posted ${fileInfo.metadata.title}${
               count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
diff --git a/src/renderer/redux/actions/subscriptions.js b/src/renderer/redux/actions/subscriptions.js
index 6f0dba49f..b98e7b7a5 100644
--- a/src/renderer/redux/actions/subscriptions.js
+++ b/src/renderer/redux/actions/subscriptions.js
@@ -2,56 +2,127 @@
 import * as ACTIONS from 'constants/action_types';
 import * as NOTIFICATION_TYPES from 'constants/notification_types';
 import type {
-  Subscription,
   Dispatch,
   SubscriptionState,
   SubscriptionNotifications,
 } from 'redux/reducers/subscriptions';
+import type { Subscription } from 'types/subscription';
 import { selectSubscriptions } from 'redux/selectors/subscriptions';
-import { Lbry, buildURI } from 'lbry-redux';
+import { Lbry, buildURI, parseURI } from 'lbry-redux';
 import { doPurchaseUri } from 'redux/actions/content';
-import { doNavigate } from 'redux/actions/navigation';
-import analytics from 'analytics';
+import Promise from 'bluebird';
+import Lbryio from 'lbryio';
 
 const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
 const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
 
-export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
-  dispatch({
-    type: ACTIONS.CHANNEL_SUBSCRIBE,
-    data: subscription,
-  });
+export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
+  const {
+    subscriptions: subscriptionState,
+    settings: { daemonSettings },
+  } = getState();
+  const { subscriptions: reduxSubscriptions } = subscriptionState;
+  const { share_usage_data: isSharingData } = daemonSettings;
 
-  analytics.apiLogSubscribe(subscription);
+  if (!isSharingData && isSharingData !== undefined) {
+    // They aren't sharing their data, subscriptions will be handled by persisted redux state
+    return;
+  }
 
-  dispatch(doCheckSubscription(subscription, true));
+  // most of this logic comes from scenarios where the db isn't synced with redux
+  // this will happen if the user stops sharing data
+  dispatch({ type: ACTIONS.FETCH_SUBSCRIPTIONS_START });
+
+  Lbryio.call('subscription', 'list')
+    .then(dbSubscriptions => {
+      const storedSubscriptions = dbSubscriptions || [];
+
+      // User has no subscriptions in db or redux
+      if (!storedSubscriptions.length && (!reduxSubscriptions || !reduxSubscriptions.length)) {
+        return [];
+      }
+
+      // There is some mismatch between redux state and db state
+      // If something is in the db, but not in redux, add it to redux
+      // If something is in redux, but not in the db, add it to the db
+      if (storedSubscriptions.length !== reduxSubscriptions.length) {
+        const dbSubMap = {};
+        const reduxSubMap = {};
+        const subsNotInDB = [];
+        const subscriptionsToReturn = reduxSubscriptions.slice();
+
+        storedSubscriptions.forEach(sub => {
+          dbSubMap[sub.claim_id] = 1;
+        });
+
+        reduxSubscriptions.forEach(sub => {
+          const { claimId } = parseURI(sub.uri);
+          reduxSubMap[claimId] = 1;
+
+          if (!dbSubMap[claimId]) {
+            subsNotInDB.push({
+              claim_id: claimId,
+              channel_name: sub.channelName,
+            });
+          }
+        });
+
+        storedSubscriptions.forEach(sub => {
+          if (!reduxSubMap[sub.claim_id]) {
+            const uri = `lbry://${sub.channel_name}#${sub.claim_id}`;
+            subscriptionsToReturn.push({ uri, channelName: sub.channel_name });
+          }
+        });
+
+        return Promise.all(subsNotInDB.map(payload => Lbryio.call('subscription', 'new', payload)))
+          .then(() => subscriptionsToReturn)
+          .catch(
+            () =>
+              // let it fail, we will try again when the navigate to the subscriptions page
+              subscriptionsToReturn
+          );
+      }
+
+      // DB is already synced, just return the subscriptions in redux
+      return reduxSubscriptions;
+    })
+    .then(subscriptions => {
+      dispatch({
+        type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
+        data: subscriptions,
+      });
+    })
+    .catch(() => {
+      dispatch({
+        type: ACTIONS.FETCH_SUBSCRIPTIONS_FAIL,
+      });
+    });
 };
 
-export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
+export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
+  dispatch: Dispatch
+) =>
   dispatch({
-    type: ACTIONS.CHANNEL_UNSUBSCRIBE,
-    data: subscription,
+    type: ACTIONS.SET_SUBSCRIPTION_LATEST,
+    data: {
+      subscription,
+      uri,
+    },
   });
 
-  analytics.apiLogUnsubscribe(subscription);
-};
-
-export const doCheckSubscriptions = () => (
-  dispatch: Dispatch,
-  getState: () => SubscriptionState
-) => {
-  const checkSubscriptionsTimer = setInterval(
-    () =>
-      selectSubscriptions(getState()).map((subscription: Subscription) =>
-        dispatch(doCheckSubscription(subscription, true))
-      ),
-    CHECK_SUBSCRIPTIONS_INTERVAL
-  );
+export const setSubscriptionNotification = (
+  subscription: Subscription,
+  uri: string,
+  notificationType: string
+) => (dispatch: Dispatch) =>
   dispatch({
-    type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
-    data: { checkSubscriptionsTimer },
+    type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
+    data: {
+      subscription,
+      uri,
+      type: notificationType,
+    },
   });
-};
 
 export const doCheckSubscription = (subscription: Subscription, notify?: boolean) => (
   dispatch: Dispatch
@@ -114,31 +185,6 @@ export const doCheckSubscription = (subscription: Subscription, notify?: boolean
   });
 };
 
-export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
-  dispatch: Dispatch
-) =>
-  dispatch({
-    type: ACTIONS.SET_SUBSCRIPTION_LATEST,
-    data: {
-      subscription,
-      uri,
-    },
-  });
-
-export const setSubscriptionNotification = (
-  subscription: Subscription,
-  uri: string,
-  notificationType: string
-) => (dispatch: Dispatch) =>
-  dispatch({
-    type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
-    data: {
-      subscription,
-      uri,
-      type: notificationType,
-    },
-  });
-
 export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
   dispatch: Dispatch
 ) =>
@@ -149,5 +195,68 @@ export const setSubscriptionNotifications = (notifications: SubscriptionNotifica
     },
   });
 
-export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) =>
-  dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });
+export const doChannelSubscribe = (subscription: Subscription) => (
+  dispatch: Dispatch,
+  getState: () => any
+) => {
+  const {
+    settings: { daemonSettings },
+  } = getState();
+  const { share_usage_data: isSharingData } = daemonSettings;
+
+  dispatch({
+    type: ACTIONS.CHANNEL_SUBSCRIBE,
+    data: subscription,
+  });
+
+  // if the user isn't sharing data, keep the subscriptions entirely in the app
+  if (isSharingData) {
+    const { claimId } = parseURI(subscription.uri);
+    // They are sharing data, we can store their subscriptions in our internal database
+    Lbryio.call('subscription', 'new', {
+      channel_name: subscription.channelName,
+      claim_id: claimId,
+    });
+  }
+
+  dispatch(doCheckSubscription(subscription, true));
+};
+
+export const doChannelUnsubscribe = (subscription: Subscription) => (
+  dispatch: Dispatch,
+  getState: () => any
+) => {
+  const {
+    settings: { daemonSettings },
+  } = getState();
+  const { share_usage_data: isSharingData } = daemonSettings;
+
+  dispatch({
+    type: ACTIONS.CHANNEL_UNSUBSCRIBE,
+    data: subscription,
+  });
+
+  if (isSharingData) {
+    const { claimId } = parseURI(subscription.uri);
+    Lbryio.call('subscription', 'delete', {
+      claim_id: claimId,
+    });
+  }
+};
+
+export const doCheckSubscriptions = () => (
+  dispatch: Dispatch,
+  getState: () => SubscriptionState
+) => {
+  const checkSubscriptionsTimer = setInterval(
+    () =>
+      selectSubscriptions(getState()).map((subscription: Subscription) =>
+        dispatch(doCheckSubscription(subscription, true))
+      ),
+    CHECK_SUBSCRIPTIONS_INTERVAL
+  );
+  dispatch({
+    type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
+    data: { checkSubscriptionsTimer },
+  });
+};
diff --git a/src/renderer/redux/reducers/subscriptions.js b/src/renderer/redux/reducers/subscriptions.js
index 7a54e2d46..348528007 100644
--- a/src/renderer/redux/reducers/subscriptions.js
+++ b/src/renderer/redux/reducers/subscriptions.js
@@ -2,12 +2,7 @@
 import * as ACTIONS from 'constants/action_types';
 import * as NOTIFICATION_TYPES from 'constants/notification_types';
 import { handleActions } from 'util/redux-utils';
-
-export type Subscription = {
-  channelName: string,
-  uri: string,
-  latest: ?string,
-};
+import type { Subscription } from 'types/subscription';
 
 export type NotificationType =
   | NOTIFICATION_TYPES.DOWNLOADING
@@ -24,8 +19,8 @@ export type SubscriptionNotifications = {
 // Subscription redux types
 export type SubscriptionState = {
   subscriptions: Array<Subscription>,
-  hasFetchedSubscriptions: boolean,
   notifications: SubscriptionNotifications,
+  loading: boolean,
 };
 
 // Subscription action types
@@ -39,10 +34,6 @@ type doChannelUnsubscribe = {
   data: Subscription,
 };
 
-type HasFetchedSubscriptions = {
-  type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS,
-};
-
 type setSubscriptionLatest = {
   type: ACTIONS.SET_SUBSCRIPTION_LATEST,
   data: {
@@ -75,10 +66,14 @@ type CheckSubscriptionCompleted = {
   type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
 };
 
+type fetchedSubscriptionsSucess = {
+  type: ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS,
+  data: Array<Subscription>,
+};
+
 export type Action =
   | doChannelSubscribe
   | doChannelUnsubscribe
-  | HasFetchedSubscriptions
   | setSubscriptionLatest
   | setSubscriptionNotification
   | CheckSubscriptionStarted
@@ -88,8 +83,8 @@ export type Dispatch = (action: Action) => any;
 
 const defaultState = {
   subscriptions: [],
-  hasFetchedSubscriptions: false,
   notifications: {},
+  loading: false,
 };
 
 export default handleActions(
@@ -122,10 +117,6 @@ export default handleActions(
         subscriptions: newSubscriptions,
       };
     },
-    [ACTIONS.HAS_FETCHED_SUBSCRIPTIONS]: (state: SubscriptionState): SubscriptionState => ({
-      ...state,
-      hasFetchedSubscriptions: true,
-    }),
     [ACTIONS.SET_SUBSCRIPTION_LATEST]: (
       state: SubscriptionState,
       action: setSubscriptionLatest
@@ -155,6 +146,22 @@ export default handleActions(
       ...state,
       notifications: action.data.notifications,
     }),
+    [ACTIONS.FETCH_SUBSCRIPTIONS_START]: (state: SubscriptionState): SubscriptionState => ({
+      ...state,
+      loading: true,
+    }),
+    [ACTIONS.FETCH_SUBSCRIPTIONS_FAIL]: (state: SubscriptionState): SubscriptionState => ({
+      ...state,
+      loading: false,
+    }),
+    [ACTIONS.FETCH_SUBSCRIPTIONS_SUCCESS]: (
+      state: SubscriptionState,
+      action: fetchedSubscriptionsSucess
+    ): SubscriptionState => ({
+      ...state,
+      loading: false,
+      subscriptions: action.data,
+    }),
   },
   defaultState
 );
diff --git a/src/renderer/redux/selectors/subscriptions.js b/src/renderer/redux/selectors/subscriptions.js
index 8f5f9d6ba..1e9ca71cd 100644
--- a/src/renderer/redux/selectors/subscriptions.js
+++ b/src/renderer/redux/selectors/subscriptions.js
@@ -1,15 +1,21 @@
 import { createSelector } from 'reselect';
-import { selectAllClaimsByChannel, selectClaimsById } from 'lbry-redux';
+import {
+  selectAllClaimsByChannel,
+  selectClaimsById,
+  selectAllFetchingChannelClaims,
+} from 'lbry-redux';
 
 // get the entire subscriptions state
 const selectState = state => state.subscriptions || {};
 
+export const selectIsFetchingSubscriptions = createSelector(selectState, state => state.loading);
+
 export const selectNotifications = createSelector(selectState, state => state.notifications);
 
 // list of saved channel names and uris
 export const selectSubscriptions = createSelector(selectState, state => state.subscriptions);
 
-export const selectSubscriptionsFromClaims = createSelector(
+export const selectSubscriptionClaims = createSelector(
   selectAllClaimsByChannel,
   selectClaimsById,
   selectSubscriptions,
@@ -37,9 +43,6 @@ export const selectSubscriptionsFromClaims = createSelector(
         });
       }
 
-      // all we really need is a uri for each claim
-      channelClaims = channelClaims.map(claim => `${claim.name}#${claim.claim_id}`);
-
       fetchedSubscriptions.push({
         claims: channelClaims,
         channelName: subscription.channelName,
@@ -50,3 +53,19 @@ export const selectSubscriptionsFromClaims = createSelector(
     return fetchedSubscriptions;
   }
 );
+
+export const selectSubscriptionsBeingFetched = createSelector(
+  selectSubscriptions,
+  selectAllFetchingChannelClaims,
+  (subscriptions, fetchingChannelClaims) => {
+    const fetchingSubscriptionMap = {};
+    subscriptions.forEach(sub => {
+      const isFetching = fetchingChannelClaims && fetchingChannelClaims[sub.uri];
+      if (isFetching) {
+        fetchingSubscriptionMap[sub.uri] = 1;
+      }
+    });
+
+    return fetchingSubscriptionMap;
+  }
+);
diff --git a/src/renderer/scss/_gui.scss b/src/renderer/scss/_gui.scss
index 9904a94f9..8fc1aeeb9 100644
--- a/src/renderer/scss/_gui.scss
+++ b/src/renderer/scss/_gui.scss
@@ -221,6 +221,9 @@ p {
   margin-top: 200px;
   text-align: center;
   font-family: 'metropolis-medium';
+  display: flex;
+  flex-direction: column;
+  align-items: center;
 }
 
 .columns {
diff --git a/src/renderer/scss/component/_card.scss b/src/renderer/scss/component/_card.scss
index e190cd177..8bd63a98d 100644
--- a/src/renderer/scss/component/_card.scss
+++ b/src/renderer/scss/component/_card.scss
@@ -299,23 +299,10 @@
     display: inline-block;
     vertical-align: top;
     margin-bottom: 60px;
+    width: calc((100% / 4) - (60px / 4));
 
-    @media only screen and (max-width: $medium-breakpoint) {
-      width: calc((100% / 3) - (40px / 3));
-
-      &:not(:nth-child(3n + 1)) {
-        margin-left: 20px;
-      }
-    }
-  }
-
-  @media only screen and (min-width: $medium-breakpoint) {
-    .card {
-      width: calc((100% / 4) - (60px / 4));
-
-      &:not(:nth-child(4n + 1)) {
-        margin-left: 20px;
-      }
+    &:not(:nth-child(4n + 1)) {
+      margin-left: 20px;
     }
   }
 }
@@ -341,8 +328,8 @@
     display: inline-block;
     vertical-align: top;
     overflow: visible;
-    // 35 px to handle to padding between cards
-    width: calc((100% / 3) - 35px);
+    // 31 px to handle to padding between cards
+    width: calc((100% / 4) - 31px);
   }
 
   .card:not(:first-of-type) {
@@ -352,13 +339,6 @@
   .card:last-of-type {
     margin-right: 20px;
   }
-
-  @media only screen and (min-width: $medium-breakpoint) {
-    .card {
-      // 31 px to handle to padding between cards
-      width: calc((100% / 4) - 31px);
-    }
-  }
 }
 
 .card__success-msg {
diff --git a/src/renderer/scss/component/_spinner.scss b/src/renderer/scss/component/_spinner.scss
index 09433cbb1..bdeda87ab 100644
--- a/src/renderer/scss/component/_spinner.scss
+++ b/src/renderer/scss/component/_spinner.scss
@@ -10,7 +10,6 @@
     height: 100%;
     width: 6px;
     margin: 0 2px;
-    background-color: var(--color-white);
     animation: sk-stretchdelay 1.2s infinite ease-in-out;
 
     &.rect2 {
@@ -31,6 +30,12 @@
   }
 }
 
+.spinner--light {
+  .rect {
+    background-color: var(--color-white);
+  }
+}
+
 .spinner--dark {
   .rect {
     background-color: var(--color-black);
diff --git a/src/renderer/store.js b/src/renderer/store.js
index 7ab581a7b..690387ffe 100644
--- a/src/renderer/store.js
+++ b/src/renderer/store.js
@@ -102,6 +102,7 @@ const store = createStore(
 const compressor = createCompressor();
 const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
 const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
+
 // We only need to persist the receiveAddress for the wallet
 const walletFilter = createFilter('wallet', ['receiveAddress']);
 
diff --git a/src/renderer/types/claim.js b/src/renderer/types/claim.js
new file mode 100644
index 000000000..2f858a9a6
--- /dev/null
+++ b/src/renderer/types/claim.js
@@ -0,0 +1,30 @@
+// @flow
+
+// Actual claim type has more values than this
+// Add them as they are used
+export type Claim = {
+  address: string,
+  amount: number,
+  claim_id: string,
+  claim_sequence: number,
+  decoded_claim: boolean,
+  depth: number,
+  effective_amount: number,
+  has_signature: boolean,
+  height: number,
+  has_signature: boolean,
+  hex: string,
+  name: string,
+  nout: number,
+  permanent_url: string,
+  channel_name: ?string,
+  txid: string,
+  nout: number,
+  signature_is_valid: boolean,
+  valid_at_height: number,
+  value: {
+    publisherSignature: ?{
+      certificateId: ?string,
+    },
+  },
+};
diff --git a/src/renderer/types/subscription.js b/src/renderer/types/subscription.js
new file mode 100644
index 000000000..dc7a4c744
--- /dev/null
+++ b/src/renderer/types/subscription.js
@@ -0,0 +1,7 @@
+// @flow
+
+export type Subscription = {
+  channelName: string, // @CryptoCandor,
+  uri: string, // lbry://@CryptoCandor#9152f3b054f692076a6882d1b58a30e8781cc8e6
+  latest: string, // substratum#b0ab143243020e7831fd070d9f871e1fda948620
+};
diff --git a/src/renderer/util/dom.js b/src/renderer/util/dom.js
new file mode 100644
index 000000000..3f93a94a8
--- /dev/null
+++ b/src/renderer/util/dom.js
@@ -0,0 +1,12 @@
+// @flow
+import * as React from 'react';
+
+// is a child component being rendered?
+export const isShowingChildren = (children: React.Node): boolean => {
+  if (Array.isArray(children)) {
+    const firstChildIndex = children.findIndex(child => child);
+    return firstChildIndex > -1;
+  }
+
+  return !!children;
+};