├── .bundle
└── config
├── .eslintrc.js
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .prettierrc.js
├── .watchmanconfig
├── Gemfile
├── LICENCE.md
├── PRIVACY.md
├── README.md
├── __tests__
└── App.test.jsx
├── android
├── app
│ ├── build.gradle
│ ├── debug.keystore
│ ├── proguard-rules.pro
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── plexmanager
│ │ │ ├── MainActivity.kt
│ │ │ └── MainApplication.kt
│ │ └── res
│ │ ├── drawable
│ │ └── rn_edit_text_material.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── strings.xml
│ │ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
├── app.json
├── babel.config.js
├── index.js
├── ios
├── .xcode.env
├── Podfile
├── plexmanager.xcodeproj
│ ├── project.pbxproj
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── plexmanager.xcscheme
├── plexmanager
│ ├── AppDelegate.h
│ ├── AppDelegate.mm
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Info.plist
│ ├── LaunchScreen.storyboard
│ └── main.m
└── plexmanagerTests
│ ├── Info.plist
│ └── plexmanagerTests.m
├── jest.config.js
├── metro.config.js
├── package.json
├── react-native.config.js
├── src
├── App.jsx
├── assets
│ ├── icons
│ │ ├── android.png
│ │ ├── apple.png
│ │ ├── chrome.png
│ │ ├── chromecast.png
│ │ ├── edge.png
│ │ ├── firefox.png
│ │ ├── github.png
│ │ ├── license.png
│ │ ├── linux.png
│ │ ├── movie.png
│ │ ├── music.png
│ │ ├── npm.png
│ │ ├── opera.png
│ │ ├── photo.png
│ │ ├── safari.png
│ │ ├── tv-show.png
│ │ ├── unknown.png
│ │ ├── user.png
│ │ ├── windows.png
│ │ └── xbox.png
│ └── img
│ │ └── logo.png
├── components
│ ├── ServerButton.jsx
│ └── Tabs.jsx
├── functions
│ ├── Darkmode.jsx
│ ├── GlobalUtiles.jsx
│ ├── ServerManageUtiles.jsx
│ ├── ServerRequest.jsx
│ ├── ServerStorage.jsx
│ └── SingleAccountUtilities.jsx
├── screens
│ ├── About.jsx
│ ├── ActiveSessions.jsx
│ ├── Activities.jsx
│ ├── EditServer.jsx
│ ├── NewServer.jsx
│ ├── NewestMovies.jsx
│ ├── ScheduledTasks.jsx
│ ├── ServerCapabilities.jsx
│ ├── ServerPreferences.jsx
│ ├── Servers.jsx
│ ├── Settings.jsx
│ ├── SingleAccount.jsx
│ ├── SingleEpisode.jsx
│ ├── SingleLibrary.jsx
│ ├── SingleMedia.jsx
│ ├── SingleSeason.jsx
│ ├── SingleServer.jsx
│ ├── SingleSession.jsx
│ └── TranscodingSessions.jsx
└── utiles
│ └── Colors.js
├── tsconfig.json
└── yarn.lock
/.bundle/config:
--------------------------------------------------------------------------------
1 | BUNDLE_PATH: "vendor/bundle"
2 | BUNDLE_FORCE_RUBY_PLATFORM: 1
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: '@react-native',
4 | };
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Smartphone (please complete the following information):**
27 | - Device: [e.g. Pixel 6]
28 | - OS: [e.g. Android 13]
29 | - SDK; [e.g. 33.0]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | ios/.xcode.env.local
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 | *.hprof
33 | .cxx/
34 | *.keystore
35 | !debug.keystore
36 |
37 | # node.js
38 | #
39 | node_modules/
40 | npm-debug.log
41 | yarn-error.log
42 |
43 | # fastlane
44 | #
45 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
46 | # screenshots whenever they are needed.
47 | # For more information about the recommended setup visit:
48 | # https://docs.fastlane.tools/best-practices/source-control/
49 |
50 | **/fastlane/report.xml
51 | **/fastlane/Preview.html
52 | **/fastlane/screenshots
53 | **/fastlane/test_output
54 |
55 | # Bundle artifact
56 | *.jsbundle
57 |
58 | # Ruby / CocoaPods
59 | /ios/Pods/
60 | /vendor/bundle/
61 |
62 | # Temporary files created by Metro to check the health of the file watcher
63 | .metro-health-check*
64 |
65 | # testing
66 | /coverage
67 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'avoid',
3 | bracketSameLine: true,
4 | bracketSpacing: false,
5 | singleQuote: true,
6 | trailingComma: 'all',
7 | };
8 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
4 | ruby ">= 2.6.10"
5 |
6 | gem 'cocoapods', '~> 1.13'
7 | gem 'activesupport', '>= 6.1.7.3', '< 7.1.0'
8 |
--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------
1 | **Privacy Policy**
2 |
3 | Sik' built the Plex Manager app as an Open Source app. This SERVICE is provided by Sik' at no cost and is intended for use as is.
4 |
5 | This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
6 |
7 | If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
8 |
9 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible at Plex Manager unless otherwise defined in this Privacy Policy.
10 |
11 | **Information Collection and Use**
12 |
13 | For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way.
14 |
15 | **Log Data**
16 |
17 | I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third-party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
18 |
19 | **Cookies**
20 |
21 | Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
22 |
23 | This Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
24 |
25 | **Service Providers**
26 |
27 | I may employ third-party companies and individuals due to the following reasons:
28 |
29 | * To facilitate our Service;
30 | * To provide the Service on our behalf;
31 | * To perform Service-related services; or
32 | * To assist us in analyzing how our Service is used.
33 |
34 | I want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
35 |
36 | **Security**
37 |
38 | I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
39 |
40 | **Links to Other Sites**
41 |
42 | This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
43 |
44 | **Children’s Privacy**
45 |
46 | I do not knowingly collect personally identifiable information from children. I encourage all children to never submit any personally identifiable information through the Application and/or Services. I encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child has provided personally identifiable information to us through the Application and/or Services, please contact us. You must also be at least 16 years of age to consent to the processing of your personally identifiable information in your country (in some countries we may allow your parent or guardian to do so on your behalf).
47 |
48 | **Changes to This Privacy Policy**
49 |
50 | I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
51 |
52 | This policy is effective as of 2023-04-29
53 |
54 | **Contact Us**
55 |
56 | If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at contact@sikelio.wtf.
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Plex Manager
3 |
4 | A cross-platform app build in [React Native](https://reactnative.dev/), based on the [Plex API](https://www.plexopedia.com/plex-media-server/api/) for managing Plex Media Servers from a phone.
5 |
6 | | Labels | Version | Branch |
7 | |-------------|-------------|----------------------------------------------------------------------|
8 | | Current | v0.0.4-beta | [0.0.4-beta](https://github.com/sikelio/plexmanager/tree/0.0.4-beta) |
9 | | Development | v0.0.5-beta | [dev](https://github.com/sikelio/plexmanager/tree/dev) |
10 |
11 | ## Install
12 |
13 | - Play Store : Deleted from the Play Store. Download it on the [release page](https://github.com/sikelio/plexmanager/releases)
14 | - App Store : [One day may be...](#ios)
15 |
16 | ## Run in dev mode
17 |
18 | Clone the project
19 |
20 | ```
21 | $ git clone https://github.com/sikelio/plexmanager
22 | ```
23 |
24 | Go to the project directory
25 |
26 | ```
27 | $ cd plex-manager
28 | ```
29 |
30 | Install dependencies
31 |
32 | ```
33 | $ yarn install
34 | ```
35 |
36 | Start the app
37 |
38 | - Android :
39 | ```
40 | $ yarn android
41 | ```
42 |
43 | - iOS (make sure to link the dependencies before)
44 |
45 | I didn't test the app on iOS so don't be surprised if it don't work
46 | ```
47 | $ yarn ios
48 | ```
49 |
50 | ## Documentation
51 |
52 | Under construction
53 |
54 | - [Getting Plex Media Server Access Token](https://github.com/sikelio/plexmanager/wiki/Getting-Plex-Media-Server-Access-Token)
55 |
56 | ## Features
57 |
58 | - Server
59 | - [X] [Server Capabilities](https://github.com/sikelio/plexmanager/issues/1)
60 | - [X] [Server Identity](https://github.com/sikelio/plexmanager/issues/2)
61 | - [X] [Server Preferences](https://github.com/sikelio/plexmanager/issues/3)
62 | - [X] [Get Accounts](https://github.com/sikelio/plexmanager/issues/4)
63 | - [X] [Get a Single Account](https://github.com/sikelio/plexmanager/issues/4)
64 | - [X] [Get Devices](https://github.com/sikelio/plexmanager/issues/5)
65 | - [~~Get a Single Device~~ (No additional infos)](https://github.com/sikelio/plexmanager/issues/6)
66 | - [X] [Get All Activities](https://github.com/sikelio/plexmanager/issues/7)
67 | - [X] [Stop an Activity](https://github.com/sikelio/plexmanager/issues/8)
68 | - [ ] [Get Transient Token](https://github.com/sikelio/plexmanager/issues/60)
69 | - [ ] [Perform Search](https://github.com/sikelio/plexmanager/issues/61)
70 | - Session
71 | - [X] [Get Active Sessions](https://github.com/sikelio/plexmanager/issues/9)
72 | - [X] [Get All Transcode Sessions](https://github.com/sikelio/plexmanager/issues/10)
73 | - [ ] [Terminate a Session](https://github.com/sikelio/plexmanager/issues/11)
74 | - [X] [Get Session History](https://github.com/sikelio/plexmanager/issues/62)
75 | - Library
76 | - [X] [Get Libraries](https://github.com/sikelio/plexmanager/issues/12)
77 | - [X] [Scan All Libraries](https://github.com/sikelio/plexmanager/issues/45)
78 | - [X] [Scan a Single Library](https://github.com/sikelio/plexmanager/issues/46)
79 | - [X] [Refresh Metadata for a Library](https://github.com/sikelio/plexmanager/issues/12)
80 | - Media
81 | - [X] [Get All Movies](https://github.com/sikelio/plexmanager/issues/13)
82 | - [X] [Get Newest Movies](https://github.com/sikelio/plexmanager/issues/14)
83 | - [ ] [Update Movie Details](https://github.com/sikelio/plexmanager/issues/15)
84 | - [X] [Get All TV Shows](https://github.com/sikelio/plexmanager/issues/16)
85 | - [X] [Get All TV Show Seasons](https://github.com/sikelio/plexmanager/issues/17)
86 | - [X] [Get All TV Show Episodes](https://github.com/sikelio/plexmanager/issues/18)
87 | - [X] [Get All Music Artists](https://github.com/sikelio/plexmanager/issues/19)
88 | - [X] [Get All Photos](https://github.com/sikelio/plexmanager/issues/20)
89 | - [X] [Get All Videos](https://github.com/sikelio/plexmanager/issues/21)
90 | - [ ] [Update Media Play Progress](https://github.com/sikelio/plexmanager/issues/22)
91 | - Playlists
92 | - [ ] [Create a Playlist for a User](https://github.com/sikelio/plexmanager/issues/63)
93 | - [ ] [View Playlists for a User](https://github.com/sikelio/plexmanager/issues/23)
94 | - [ ] [View a Single Playlist](https://github.com/sikelio/plexmanager/issues/24)
95 | - [ ] [View Items in a Playlist](https://github.com/sikelio/plexmanager/issues/25)
96 | - Maintenance
97 | - [X] [Empty trash](https://github.com/sikelio/plexmanager/issues/26)
98 | - [X] [Clean Bundles](https://github.com/sikelio/plexmanager/issues/27)
99 | - [X] [Optimize Database](https://github.com/sikelio/plexmanager/issues/28)
100 | - Scheduled Tasks
101 | - [X] [Get All Scheduled Tasks](https://github.com/sikelio/plexmanager/issues/29)
102 | - [X] [Run Backup Database Task](https://github.com/sikelio/plexmanager/issues/30)
103 | - [X] [Stop Backup Database Task](https://github.com/sikelio/plexmanager/issues/66)
104 | - [X] [Stop All Scheduled Tasks](https://github.com/sikelio/plexmanager/issues/64)
105 | - [X] [Run Optimize Database Task](https://github.com/sikelio/plexmanager/issues/67)
106 | - [X] [Stop Optimize Database Task](https://github.com/sikelio/plexmanager/issues/68)
107 | - [X] [Run Clean Old Bundles Task](https://github.com/sikelio/plexmanager/issues/69)
108 | - [X] [Stop Clean Old Bundles Task](https://github.com/sikelio/plexmanager/issues/70)
109 | - [X] [Run Clean Old Cache Files Task](https://github.com/sikelio/plexmanager/issues/71)
110 | - [X] [Stop Clean Old Cache Files Task](https://github.com/sikelio/plexmanager/issues/72)
111 | - [X] [Run Refresh Libraries Task](https://github.com/sikelio/plexmanager/issues/73)
112 | - [X] [Stop Refresh Libraries Task](https://github.com/sikelio/plexmanager/issues/74)
113 | - Troubleshooting
114 | - [X] [Download the Databases](https://github.com/sikelio/plexmanager/issues/31)
115 | - [X] [Download the Logs](https://github.com/sikelio/plexmanager/issues/32)
116 |
117 | ## Author
118 | - [@sikelio](https://www.github.com/sikelio)
119 |
120 | ## Supports
121 |
122 | ### Android
123 | The app is configured to be installed on Android >= 5.0 (SDK 21) but the app is currently tested on an Android 33 version. Some tests, will be done in the future. If you are getting trouble to install the app, please open an issue.
124 |
125 | ### iOS
126 | I don't own an Apple device and an Apple developer licence for publishing the app on the App Store. For this reason the app is currently not available for iOS devices.
127 |
128 | ## License
129 |
130 | [GLP v3](https://github.com/sikelio/plexmanager/blob/main/LICENCE.md)
131 |
--------------------------------------------------------------------------------
/__tests__/App.test.jsx:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import 'react-native';
6 | import React from 'react';
7 | import App from '../src/App';
8 |
9 | // Note: import explicitly to use the types shipped with jest.
10 | import {it} from '@jest/globals';
11 |
12 | // Note: test renderer must be required after react-native.
13 | import renderer from 'react-test-renderer';
14 |
15 | it('renders correctly', () => {
16 | renderer.create();
17 | });
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 | apply plugin: "org.jetbrains.kotlin.android"
3 | apply plugin: "com.facebook.react"
4 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
5 |
6 | /**
7 | * This is the configuration block to customize your React Native Android app.
8 | * By default you don't need to apply any configuration, just uncomment the lines you need.
9 | */
10 | react {
11 | /* Folders */
12 | // The root of your project, i.e. where "package.json" lives. Default is '..'
13 | // root = file("../")
14 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native
15 | // reactNativeDir = file("../node_modules/react-native")
16 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
17 | // codegenDir = file("../node_modules/@react-native/codegen")
18 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
19 | // cliFile = file("../node_modules/react-native/cli.js")
20 |
21 | /* Variants */
22 | // The list of variants to that are debuggable. For those we're going to
23 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'.
24 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
25 | // debuggableVariants = ["liteDebug", "prodDebug"]
26 |
27 | /* Bundling */
28 | // A list containing the node command and its flags. Default is just 'node'.
29 | // nodeExecutableAndArgs = ["node"]
30 | //
31 | // The command to run when bundling. By default is 'bundle'
32 | // bundleCommand = "ram-bundle"
33 | //
34 | // The path to the CLI configuration file. Default is empty.
35 | // bundleConfig = file(../rn-cli.config.js)
36 | //
37 | // The name of the generated asset file containing your JS bundle
38 | // bundleAssetName = "MyApplication.android.bundle"
39 | //
40 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
41 | // entryFile = file("../js/MyApplication.android.js")
42 | //
43 | // A list of extra flags to pass to the 'bundle' commands.
44 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
45 | // extraPackagerArgs = []
46 |
47 | /* Hermes Commands */
48 | // The hermes compiler command to run. By default it is 'hermesc'
49 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
50 | //
51 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
52 | // hermesFlags = ["-O", "-output-source-map"]
53 | }
54 |
55 | /**
56 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode.
57 | */
58 | def enableProguardInReleaseBuilds = false
59 |
60 | /**
61 | * The preferred build flavor of JavaScriptCore (JSC)
62 | *
63 | * For example, to use the international variant, you can use:
64 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
65 | *
66 | * The international variant includes ICU i18n library and necessary data
67 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
68 | * give correct results when using with locales other than en-US. Note that
69 | * this variant is about 6MiB larger per architecture than default.
70 | */
71 | def jscFlavor = 'org.webkit:android-jsc:+'
72 |
73 | android {
74 | ndkVersion rootProject.ext.ndkVersion
75 |
76 | buildToolsVersion rootProject.ext.buildToolsVersion
77 | compileSdk rootProject.ext.compileSdkVersion
78 |
79 | namespace "wtf.plexmanager"
80 | defaultConfig {
81 | applicationId "wtf.plexmanager"
82 | minSdkVersion rootProject.ext.minSdkVersion
83 | targetSdkVersion rootProject.ext.targetSdkVersion
84 | versionCode 4
85 | versionName "0.0.4-beta"
86 | }
87 |
88 | signingConfigs {
89 | debug {
90 | storeFile file('debug.keystore')
91 | storePassword 'android'
92 | keyAlias 'androiddebugkey'
93 | keyPassword 'android'
94 | }
95 | release {
96 | if (project.hasProperty('PLEXMANAGER_UPLOAD_STORE_FILE')) {
97 | storeFile file(PLEXMANAGER_UPLOAD_STORE_FILE)
98 | storePassword PLEXMANAGER_UPLOAD_STORE_PASSWORD
99 | keyAlias PLEXMANAGER_UPLOAD_KEY_ALIAS
100 | keyPassword PLEXMANAGER_UPLOAD_KEY_PASSWORD
101 | }
102 | }
103 | }
104 | buildTypes {
105 | debug {
106 | signingConfig signingConfigs.debug
107 | }
108 | release {
109 | // Caution! In production, you need to generate your own keystore file.
110 | // see https://reactnative.dev/docs/signed-apk-android.
111 | signingConfig signingConfigs.release
112 | minifyEnabled enableProguardInReleaseBuilds
113 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
114 | }
115 | }
116 | }
117 |
118 | dependencies {
119 | // The version of react-native is set by the React Native Gradle Plugin
120 | implementation("com.facebook.react:react-android")
121 | implementation("com.facebook.react:flipper-integration")
122 |
123 | if (hermesEnabled.toBoolean()) {
124 | implementation("com.facebook.react:hermes-android")
125 | } else {
126 | implementation jscFlavor
127 | }
128 | }
129 |
130 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
131 |
--------------------------------------------------------------------------------
/android/app/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/debug.keystore
--------------------------------------------------------------------------------
/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/plexmanager/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package wtf.plexmanager
2 |
3 | import com.facebook.react.ReactActivity
4 | import com.facebook.react.ReactActivityDelegate
5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
6 | import com.facebook.react.defaults.DefaultReactActivityDelegate
7 |
8 | class MainActivity : ReactActivity() {
9 |
10 | /**
11 | * Returns the name of the main component registered from JavaScript. This is used to schedule
12 | * rendering of the component.
13 | */
14 | override fun getMainComponentName(): String = "plexmanager"
15 |
16 | /**
17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
19 | */
20 | override fun createReactActivityDelegate(): ReactActivityDelegate =
21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
22 | }
23 |
--------------------------------------------------------------------------------
/android/app/src/main/java/com/plexmanager/MainApplication.kt:
--------------------------------------------------------------------------------
1 | package wtf.plexmanager
2 |
3 | import android.app.Application
4 | import com.facebook.react.PackageList
5 | import com.facebook.react.ReactApplication
6 | import com.facebook.react.ReactHost
7 | import com.facebook.react.ReactNativeHost
8 | import com.facebook.react.ReactPackage
9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
11 | import com.facebook.react.defaults.DefaultReactNativeHost
12 | import com.facebook.react.flipper.ReactNativeFlipper
13 | import com.facebook.soloader.SoLoader
14 |
15 | class MainApplication : Application(), ReactApplication {
16 |
17 | override val reactNativeHost: ReactNativeHost =
18 | object : DefaultReactNativeHost(this) {
19 | override fun getPackages(): List {
20 | // Packages that cannot be autolinked yet can be added manually here, for example:
21 | // packages.add(new MyReactNativePackage());
22 | return PackageList(this).packages
23 | }
24 |
25 | override fun getJSMainModuleName(): String = "index"
26 |
27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
28 |
29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
31 | }
32 |
33 | override val reactHost: ReactHost
34 | get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
35 |
36 | override fun onCreate() {
37 | super.onCreate()
38 | SoLoader.init(this, false)
39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
40 | // If you opted-in for the New Architecture, we load the native entry point for this app.
41 | load()
42 | }
43 | ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Plex Manager
3 |
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | buildToolsVersion = "34.0.0"
4 | minSdkVersion = 21
5 | compileSdkVersion = 34
6 | targetSdkVersion = 34
7 | ndkVersion = "25.1.8937393"
8 | kotlinVersion = "1.8.0"
9 | }
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | dependencies {
15 | classpath("com.android.tools.build:gradle")
16 | classpath("com.facebook.react:react-native-gradle-plugin")
17 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
18 | }
19 | }
20 |
21 | apply plugin: "com.facebook.react.rootproject"
22 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Use this property to specify which architecture you want to build.
28 | # You can also override it from the CLI using
29 | # ./gradlew -PreactNativeArchitectures=x86_64
30 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
31 |
32 | # Use this property to enable support to the new architecture.
33 | # This will allow you to use TurboModules and the Fabric render in
34 | # your application. You should enable this flag either if you want
35 | # to write custom TurboModules/Fabric components OR use libraries that
36 | # are providing them.
37 | newArchEnabled=false
38 |
39 | # Use this property to enable or disable the Hermes JS engine.
40 | # If set to false, you will be using JSC instead.
41 | hermesEnabled=true
42 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command;
206 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
207 | # shell script including quotes and variable substitutions, so put them in
208 | # double quotes to make sure that they get re-expanded; and
209 | # * put everything else in single quotes, so that it's not re-expanded.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'plexmanager'
2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
3 | include ':app'
4 | includeBuild('../node_modules/@react-native/gradle-plugin')
5 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plexmanager",
3 | "displayName": "Plex Manager"
4 | }
5 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | 'module:@react-native/babel-preset'
4 | ]
5 | };
6 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @format
3 | */
4 |
5 | import { AppRegistry } from 'react-native';
6 | import App from './src/App';
7 | import { name as appName } from './app.json';
8 |
9 | AppRegistry.registerComponent(appName, () => App);
10 |
--------------------------------------------------------------------------------
/ios/.xcode.env:
--------------------------------------------------------------------------------
1 | # This `.xcode.env` file is versioned and is used to source the environment
2 | # used when running script phases inside Xcode.
3 | # To customize your local environment, you can create an `.xcode.env.local`
4 | # file that is not versioned.
5 |
6 | # NODE_BINARY variable contains the PATH to the node executable.
7 | #
8 | # Customize the NODE_BINARY variable here.
9 | # For example, to use nvm with brew, add the following line
10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use
11 | export NODE_BINARY=$(command -v node)
12 |
--------------------------------------------------------------------------------
/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Resolve react_native_pods.rb with node to allow for hoisting
2 | require Pod::Executable.execute_command('node', ['-p',
3 | 'require.resolve(
4 | "react-native/scripts/react_native_pods.rb",
5 | {paths: [process.argv[1]]},
6 | )', __dir__]).strip
7 |
8 | platform :ios, min_ios_version_supported
9 | prepare_react_native_project!
10 |
11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
13 | #
14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
15 | # ```js
16 | # module.exports = {
17 | # dependencies: {
18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
19 | # ```
20 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
21 |
22 | linkage = ENV['USE_FRAMEWORKS']
23 | if linkage != nil
24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
25 | use_frameworks! :linkage => linkage.to_sym
26 | end
27 |
28 | target 'RnDiffApp' do
29 | config = use_native_modules!
30 |
31 | use_react_native!(
32 | :path => config[:reactNativePath],
33 | # Enables Flipper.
34 | #
35 | # Note that if you have use_frameworks! enabled, Flipper will not work and
36 | # you should disable the next line.
37 | :flipper_configuration => flipper_config,
38 | # An absolute path to your application root.
39 | :app_path => "#{Pod::Config.instance.installation_root}/.."
40 | )
41 |
42 | target 'RnDiffAppTests' do
43 | inherit! :complete
44 | # Pods for testing
45 | end
46 |
47 | post_install do |installer|
48 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
49 | react_native_post_install(
50 | installer,
51 | config[:reactNativePath],
52 | :mac_catalyst_enabled => false
53 | )
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/ios/plexmanager.xcodeproj/xcshareddata/xcschemes/plexmanager.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/ios/plexmanager/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : RCTAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/plexmanager/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 |
5 | @implementation AppDelegate
6 |
7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
8 | {
9 | self.moduleName = @"RnDiffApp";
10 | // You can add your custom initial props in the dictionary below.
11 | // They will be passed down to the ViewController used by React Native.
12 | self.initialProps = @{};
13 |
14 | return [super application:application didFinishLaunchingWithOptions:launchOptions];
15 | }
16 |
17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
18 | {
19 | return [self getBundleURL];
20 | }
21 |
22 | - (NSURL *)getBundleURL
23 | {
24 | #if DEBUG
25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
26 | #else
27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
28 | #endif
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/ios/plexmanager/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ios-marketing",
45 | "scale" : "1x",
46 | "size" : "1024x1024"
47 | }
48 | ],
49 | "info" : {
50 | "author" : "xcode",
51 | "version" : 1
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ios/plexmanager/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/plexmanager/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | RnDiffApp
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 |
30 | NSAllowsArbitraryLoads
31 |
32 | NSAllowsLocalNetworking
33 |
34 |
35 | NSLocationWhenInUseUsageDescription
36 |
37 | UILaunchStoryboardName
38 | LaunchScreen
39 | UIRequiredDeviceCapabilities
40 |
41 | armv7
42 |
43 | UISupportedInterfaceOrientations
44 |
45 | UIInterfaceOrientationPortrait
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 | UIViewControllerBasedStatusBarAppearance
50 |
51 |
52 |
--------------------------------------------------------------------------------
/ios/plexmanager/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/ios/plexmanager/main.m:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 | @autoreleasepool {
8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ios/plexmanagerTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/ios/plexmanagerTests/plexmanagerTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface plexmanagerTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation plexmanagerTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(
38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
39 | if (level >= RCTLogLevelError) {
40 | redboxError = message;
41 | }
42 | });
43 | #endif
44 |
45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
48 |
49 | foundElement = [self findSubviewInView:vc.view
50 | matching:^BOOL(UIView *view) {
51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
52 | return YES;
53 | }
54 | return NO;
55 | }];
56 | }
57 |
58 | #ifdef DEBUG
59 | RCTSetLogFunction(RCTDefaultLogFunction);
60 | #endif
61 |
62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'react-native',
3 | transform: {
4 | '^.+\\.(ts|tsx)?$': 'ts-jest',
5 | '^.+\\.(js|jsx)$': 'babel-jest',
6 | }
7 | };
8 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
2 |
3 | /**
4 | * Metro configuration
5 | * https://facebook.github.io/metro/docs/configuration
6 | *
7 | * @type {import('metro-config').MetroConfig}
8 | */
9 | const config = {};
10 |
11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config);
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plexmanager",
3 | "version": "0.0.4-beta",
4 | "license": "LGPL-3.0-only",
5 | "private": false,
6 | "scripts": {
7 | "android": "react-native run-android",
8 | "ios": "react-native run-ios",
9 | "android-release": "react-native run-android --mode=release",
10 | "lint": "eslint .",
11 | "start": "react-native start",
12 | "test": "jest"
13 | },
14 | "dependencies": {
15 | "@react-native-async-storage/async-storage": "^1.21.0",
16 | "@react-native-community/netinfo": "^11.1.0",
17 | "@react-native-picker/picker": "^2.6.1",
18 | "@react-navigation/bottom-tabs": "^6.5.11",
19 | "@react-navigation/material-bottom-tabs": "^6.2.19",
20 | "@react-navigation/native": "^6.1.9",
21 | "@react-navigation/native-stack": "^6.9.17",
22 | "@rneui/base": "^4.0.0-rc.8",
23 | "@rneui/themed": "^4.0.0-rc.8",
24 | "axios": "^1.6.2",
25 | "formik": "^2.4.5",
26 | "react": "18.2.0",
27 | "react-native": "0.73.0",
28 | "react-native-fast-image": "^8.6.3",
29 | "react-native-loading-spinner-overlay": "^3.0.1",
30 | "react-native-paper": "^5.11.3",
31 | "react-native-safe-area-context": "^4.8.0",
32 | "react-native-screens": "^3.29.0",
33 | "react-native-vector-icons": "^10.0.2"
34 | },
35 | "devDependencies": {
36 | "@babel/core": "^7.20.0",
37 | "@babel/preset-env": "^7.20.0",
38 | "@babel/runtime": "^7.20.0",
39 | "@react-native/babel-preset": "^0.73.18",
40 | "@react-native/eslint-config": "^0.73.1",
41 | "@react-native/metro-config": "^0.73.2",
42 | "@react-native/typescript-config": "^0.73.1",
43 | "@types/react": "^18.2.6",
44 | "@types/react-test-renderer": "^18.0.0",
45 | "babel-jest": "^29.6.3",
46 | "eslint": "^8.19.0",
47 | "jest": "^29.6.3",
48 | "prettier": "2.8.8",
49 | "react-test-renderer": "18.2.0",
50 | "ts-jest": "^29.1.1",
51 | "typescript": "5.0.4"
52 | },
53 | "engines": {
54 | "node": ">=18"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/react-native.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | project: {
3 | ios: {
4 | automaticPodsInstallation: true
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavigationContainer } from '@react-navigation/native';
3 | import { createNativeStackNavigator } from '@react-navigation/native-stack';
4 | import Tabs from './components/Tabs';
5 | import SingleServer from './screens/SingleServer';
6 | import EditServer from './screens/EditServer';
7 | import SingleSession from './screens/SingleSession';
8 | import SingleAccount from './screens/SingleAccount';
9 | import ServerPreferences from './screens/ServerPreferences';
10 | import SingleLibrary from './screens/SingleLibrary';
11 | import SingleMedia from './screens/SingleMedia';
12 | import ScheduledTasks from './screens/ScheduledTasks';
13 | import SingleSeason from './screens/SingleSeason';
14 | import SingleEpisode from './screens/SingleEpisode';
15 | import NewestMovies from './screens/NewestMovies';
16 | import ActiveSessions from './screens/ActiveSessions';
17 | import TranscodingSessions from './screens/TranscodingSessions';
18 | import Activities from './screens/Activities';
19 | import ServerCapabilities from './screens/ServerCapabilities';
20 | import Colors from './utiles/Colors';
21 |
22 | const Stack = createNativeStackNavigator();
23 |
24 | export default class App extends React.Component {
25 | render() {
26 | return (
27 |
28 |
29 |
34 | ({
38 | title: route.params.title,
39 | headerStyle: {
40 | backgroundColor: Colors.PlexBlack
41 | },
42 | headerTitleStyle: {
43 | color: Colors.White
44 | },
45 | headerTintColor: Colors.White
46 | })}
47 | />
48 | ({
52 | title: `Edit ${route.params.title}`,
53 | headerStyle: {
54 | backgroundColor: Colors.PlexBlack
55 | },
56 | headerTitleStyle: {
57 | color: Colors.White
58 | },
59 | headerTintColor: Colors.White
60 | })}
61 | />
62 | ({
66 | title: `Manage session - ${route.params.title}`,
67 | headerStyle: {
68 | backgroundColor: Colors.PlexBlack
69 | },
70 | headerTitleStyle: {
71 | color: Colors.White
72 | },
73 | headerTintColor: Colors.White
74 | })}
75 | />
76 | ({
80 | title: `User : ${route.params.title}`,
81 | headerStyle: {
82 | backgroundColor: Colors.PlexBlack
83 | },
84 | headerTitleStyle: {
85 | color: Colors.White
86 | },
87 | headerTintColor: Colors.White
88 | })}
89 | />
90 | ({
94 | title: `Server Preferences`,
95 | headerStyle: {
96 | backgroundColor: Colors.PlexBlack
97 | },
98 | headerTitleStyle: {
99 | color: Colors.White
100 | },
101 | headerTintColor: Colors.White
102 | })}
103 | />
104 | ({
108 | title: `Library : ${route.params.title}`,
109 | headerStyle: {
110 | backgroundColor: Colors.PlexBlack
111 | },
112 | headerTitleStyle: {
113 | color: Colors.White
114 | },
115 | headerTintColor: Colors.White
116 | })}
117 | />
118 | ({
122 | title: route.params.title,
123 | headerStyle: {
124 | backgroundColor: Colors.PlexBlack
125 | },
126 | headerTitleStyle: {
127 | color: Colors.White
128 | },
129 | headerTintColor: Colors.White
130 | })}
131 | />
132 | ({
136 | title: 'Scheduled tasks',
137 | headerStyle: {
138 | backgroundColor: Colors.PlexBlack
139 | },
140 | headerTitleStyle: {
141 | color: Colors.White
142 | },
143 | headerTintColor: Colors.White
144 | })}
145 | />
146 | ({
150 | title: route.params.title,
151 | headerStyle: {
152 | backgroundColor: Colors.PlexBlack
153 | },
154 | headerTitleStyle: {
155 | color: Colors.White
156 | },
157 | headerTintColor: Colors.White
158 | })}
159 | />
160 | ({
164 | title: route.params.title,
165 | headerStyle: {
166 | backgroundColor: Colors.PlexBlack
167 | },
168 | headerTitleStyle: {
169 | color: Colors.White
170 | },
171 | headerTintColor: Colors.White
172 | })}
173 | />
174 | ({
178 | title: route.params.title,
179 | headerStyle: {
180 | backgroundColor: Colors.PlexBlack
181 | },
182 | headerTitleStyle: {
183 | color: Colors.White
184 | },
185 | headerTintColor: Colors.White
186 | })}
187 | />
188 | ({
192 | title: 'Active Sessions',
193 | headerStyle: {
194 | backgroundColor: Colors.PlexBlack
195 | },
196 | headerTitleStyle: {
197 | color: Colors.White
198 | },
199 | headerTintColor: Colors.White
200 | })}
201 | />
202 | ({
206 | title: 'Transcoding Sessions',
207 | headerStyle: {
208 | backgroundColor: Colors.PlexBlack
209 | },
210 | headerTitleStyle: {
211 | color: Colors.White
212 | },
213 | headerTintColor: Colors.White
214 | })}
215 | />
216 | ({
220 | title: 'All Activities',
221 | headerStyle: {
222 | backgroundColor: Colors.PlexBlack
223 | },
224 | headerTitleStyle: {
225 | color: Colors.White
226 | },
227 | headerTintColor: Colors.White
228 | })}
229 | />
230 | ({
234 | title: 'Server Capabilities',
235 | headerStyle: {
236 | backgroundColor: Colors.PlexBlack
237 | },
238 | headerTitleStyle: {
239 | color: Colors.White
240 | },
241 | headerTintColor: Colors.White
242 | })}
243 | />
244 |
245 |
246 | );
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/assets/icons/android.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/android.png
--------------------------------------------------------------------------------
/src/assets/icons/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/apple.png
--------------------------------------------------------------------------------
/src/assets/icons/chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/chrome.png
--------------------------------------------------------------------------------
/src/assets/icons/chromecast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/chromecast.png
--------------------------------------------------------------------------------
/src/assets/icons/edge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/edge.png
--------------------------------------------------------------------------------
/src/assets/icons/firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/firefox.png
--------------------------------------------------------------------------------
/src/assets/icons/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/github.png
--------------------------------------------------------------------------------
/src/assets/icons/license.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/license.png
--------------------------------------------------------------------------------
/src/assets/icons/linux.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/linux.png
--------------------------------------------------------------------------------
/src/assets/icons/movie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/movie.png
--------------------------------------------------------------------------------
/src/assets/icons/music.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/music.png
--------------------------------------------------------------------------------
/src/assets/icons/npm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/npm.png
--------------------------------------------------------------------------------
/src/assets/icons/opera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/opera.png
--------------------------------------------------------------------------------
/src/assets/icons/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/photo.png
--------------------------------------------------------------------------------
/src/assets/icons/safari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/safari.png
--------------------------------------------------------------------------------
/src/assets/icons/tv-show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/tv-show.png
--------------------------------------------------------------------------------
/src/assets/icons/unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/unknown.png
--------------------------------------------------------------------------------
/src/assets/icons/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/user.png
--------------------------------------------------------------------------------
/src/assets/icons/windows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/windows.png
--------------------------------------------------------------------------------
/src/assets/icons/xbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/icons/xbox.png
--------------------------------------------------------------------------------
/src/assets/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sikelio/plexmanager/791c3b42ed3070df41842dcb0b52d33d61e50486/src/assets/img/logo.png
--------------------------------------------------------------------------------
/src/components/ServerButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet } from 'react-native';
3 | import { Button } from '@rneui/themed';
4 | import Icon from 'react-native-vector-icons/FontAwesome5';
5 |
6 | class ServerButton extends React.Component {
7 | localStyle = StyleSheet.create({
8 | cardBtn: {
9 | marginRight: 10
10 | }
11 | });
12 |
13 | constructor(props) {
14 | super(props);
15 |
16 | this.state = {
17 | iconName: this.props.iconName,
18 | iconColor: this.props.iconColor,
19 | backgroundColor: this.props.backgroundColor,
20 | btnTitle: this.props.btnTitle,
21 | onPress: this.props.onPress
22 | };
23 | }
24 |
25 | render() {
26 | return (
27 |
35 | }
36 | buttonStyle={{
37 | backgroundColor: this.state.backgroundColor
38 | }}
39 | title={this.state.btnTitle}
40 | onPress={this.state.onPress}
41 | />
42 | );
43 | }
44 | }
45 |
46 | export default ServerButton;
47 |
--------------------------------------------------------------------------------
/src/components/Tabs.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
3 | import Icon from 'react-native-vector-icons/FontAwesome5';
4 | import NewServer from '../screens/NewServer';
5 | import Servers from "../screens/Servers";
6 | import About from "../screens/About";
7 | import Colors from '../utiles/Colors';
8 |
9 | const Tab = createMaterialBottomTabNavigator();
10 |
11 | class Tabs extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | }
15 |
16 | render() {
17 | return (
18 |
26 | (
32 |
33 | )
34 | }}
35 | />
36 |
37 | (
43 |
44 | )
45 | }}
46 | />
47 |
48 | (
54 |
55 | )
56 | }}
57 | />
58 |
59 | );
60 | }
61 | }
62 |
63 | export default Tabs;
64 |
--------------------------------------------------------------------------------
/src/functions/Darkmode.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import AsyncStorage from "@react-native-async-storage/async-storage";
3 | // Components
4 | import { Alert } from "react-native";
5 |
6 | const getDarkmode = async () => {
7 | const value = await AsyncStorage.getItem('darkmode');
8 |
9 | if (JSON.parse(value) === null) {
10 | return false;
11 | }
12 |
13 | return JSON.parse(value);
14 | }
15 |
16 | const getDarkmodeSwitch = (setDarkmode) => {
17 | AsyncStorage.getItem('darkmode').then((value) => {
18 | if (value !== null) {
19 | setDarkmode(JSON.parse(value));
20 | } else {
21 | setDarkmode(false);
22 | }
23 | });
24 | }
25 |
26 | const saveDarkmode = async (value) => {
27 | try {
28 | await AsyncStorage.setItem('darkmode', JSON.stringify(value));
29 | } catch (error) {
30 | Alert.alert('Error', 'Something went wrong');
31 | }
32 | };
33 |
34 | export { saveDarkmode, getDarkmodeSwitch, getDarkmode }
35 |
--------------------------------------------------------------------------------
/src/functions/GlobalUtiles.jsx:
--------------------------------------------------------------------------------
1 | export const getDateFromTimestamp = (timestamp) => {
2 | const date = new Date(timestamp * 1000);
3 |
4 | return `${("0" + date.getDate()).slice(-2)}/${("0" + (date.getMonth() + 1)).slice(-2)}/${date.getFullYear()}`;
5 | }
6 |
7 | export const getTimeFromTimestamp = (timestamp) => {
8 | const date = new Date(timestamp);
9 |
10 | return `${("0" + date.getHours()).slice(-2)}:${("0" + date.getMinutes()).slice(-2)}:${("0" + date.getSeconds()).slice(-2)}`;
11 | }
12 |
--------------------------------------------------------------------------------
/src/functions/ServerManageUtiles.jsx:
--------------------------------------------------------------------------------
1 | export const sessionTitle = (session) => {
2 | if (session.type === 'episode') {
3 | return `${session.originalTitle} - ${session.title}`;
4 | }
5 |
6 | return session.originalTitle;
7 | }
8 |
9 | export const historyTitle = (session) => {
10 | if (session.type === 'episode') {
11 | return `${session.grandparentTitle} - ${session.title}`;
12 | }
13 |
14 | return session.title;
15 | }
16 |
17 | export const getDeviceIcon = (device) => {
18 | let icon;
19 |
20 | switch (device) {
21 | case 'Firefox':
22 | icon = require('../assets/icons/firefox.png');
23 | break;
24 | case 'windows':
25 | case 'Windows':
26 | icon = require('../assets/icons/windows.png');
27 | break;
28 | case 'Microsoft Edge':
29 | icon = require('../assets/icons/edge.png');
30 | break;
31 | case 'Linux':
32 | icon = require('../assets/icons/linux.png');
33 | break;
34 | case 'iOS':
35 | icon = require('../assets/icons/apple.png');
36 | break;
37 | case 'Chrome':
38 | icon = require('../assets/icons/chrome.png');
39 | break;
40 | case 'Android':
41 | icon = require('../assets/icons/android.png');
42 | break;
43 | case 'Safari':
44 | icon = require('../assets/icons/safari.png');
45 | break;
46 | case 'Opera':
47 | icon = require('../assets/icons/opera.png');
48 | break;
49 | case 'Chromecast':
50 | icon = require('../assets/icons/chromecast.png')
51 | break;
52 | case 'Xbox':
53 | icon = require('../assets/icons/xbox.png')
54 | break;
55 | default:
56 | icon = require('../assets/icons/unknown.png');
57 | break;
58 | }
59 |
60 | return icon;
61 | }
62 |
63 | export const getLibraryIcon = (type) => {
64 | let icon;
65 |
66 | switch (type) {
67 | case 'movie':
68 | icon = require('../assets/icons/movie.png');
69 | break;
70 | case 'show':
71 | icon = require('../assets/icons/tv-show.png');
72 | break;
73 | case 'artist':
74 | icon = require('../assets/icons/music.png');
75 | break;
76 | case 'photo':
77 | icon = require('../assets/icons/photo.png');
78 | break;
79 | default:
80 | icon = require('../assets/icons/unknown.png');
81 | break;
82 | }
83 |
84 | return icon;
85 | }
86 |
87 | export const getHistoryUser = (session, users) => {
88 | let sessionUser = users.filter((user) => {
89 | return session.accountID === user.id;
90 | });
91 |
92 | return sessionUser[0].name;
93 | }
94 |
95 | export const getPreferenceLabel = (preference) => {
96 | if (!preference.label) {
97 | return preference.id;
98 | }
99 |
100 | return preference.label;
101 | }
102 |
103 | export const getPreferenceGroupName = (preference) => {
104 | if (!preference.group) {
105 | return 'none';
106 | }
107 |
108 | return preference.group;
109 | }
110 |
111 | export const getPreferenceValue = (preference) => {
112 | if (!preference.value) {
113 | return 'none';
114 | }
115 |
116 | return preference.value.toString();
117 | }
118 |
119 | export const getPreferenceSummary = (preference) => {
120 | if (!preference.summary) {
121 | return 'none';
122 | }
123 |
124 | return preference.summary;
125 | }
126 |
--------------------------------------------------------------------------------
/src/functions/ServerRequest.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import React from "react";
3 | import axios from "axios";
4 | // Components
5 | import { Alert } from "react-native";
6 |
7 | export const sendRequest = (url) => {
8 | axios.get(url).then((data) => {
9 | Alert.alert('Success', 'Request sent');
10 | }).catch((err) => {
11 | Alert.alert('Error', 'Something went wrong');
12 | });
13 | }
14 |
15 | export const sendPutRequest = (url) => {
16 | axios.put(url).then((data) => {
17 | Alert.alert('Success', 'Request sent');
18 | }).catch((err) => {
19 | Alert.alert('Error', 'Something went wrong');
20 | });
21 | }
22 |
23 | export const sendPostRequest = (url) => {
24 | axios.post(url).then((data) => {
25 | Alert.alert('Success', 'Request sent');
26 | }).catch((err) => {
27 | Alert.alert('Error', 'Something went wrong');
28 | });
29 | }
30 |
31 | export const sendDeleteRequest = (url) => {
32 | axios.delete(url).then((data) => {
33 | Alert.alert('Success', 'Request sent');
34 | }).catch((err) => {
35 | Alert.alert('Error', 'Something went wrong');
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/src/functions/ServerStorage.jsx:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | import AsyncStorage from '@react-native-async-storage/async-storage';
3 |
4 | /**
5 | * Store a server in array
6 | * @param data Servers data
7 | */
8 | export const storeServer = async (data) => {
9 | try {
10 | const serverStorage = await AsyncStorage.getItem('servers');
11 |
12 | if (serverStorage == null) {
13 | await AsyncStorage.setItem('servers', JSON.stringify([data]));
14 | } else {
15 | const rawData = JSON.parse(serverStorage);
16 | rawData.push(data);
17 |
18 | await AsyncStorage.setItem('servers', JSON.stringify(rawData));
19 | }
20 | } catch (e) {
21 | console.error(e);
22 | }
23 | }
24 |
25 | /**
26 | * Edit stored server
27 | * @param data Edited server data
28 | * @param index Servers index
29 | */
30 | export const editServer = async (data, index) => {
31 | const serverStorage = await AsyncStorage.getItem('servers');
32 |
33 | if (serverStorage == null) {
34 | await AsyncStorage.setItem('servers', JSON.stringify([data]));
35 | } else {
36 | const rawData = JSON.parse(serverStorage);
37 | rawData[index] = data;
38 |
39 | await AsyncStorage.setItem('servers', JSON.stringify(rawData));
40 | }
41 | }
42 |
43 | /**
44 | * Get server list
45 | */
46 | export const getServer = async () => {
47 | let servers = await AsyncStorage.getItem('servers');
48 |
49 | if (servers === null) {
50 | servers = JSON.stringify([]);
51 | }
52 |
53 | return servers;
54 | }
55 |
56 | export const deleteServer = async (index) => {
57 | try {
58 | let servers = await AsyncStorage.getItem('servers');
59 | if (servers != null) {
60 | let data = JSON.parse(servers);
61 | data.splice(index, 1);
62 |
63 | return await AsyncStorage.setItem('servers', JSON.stringify(data));
64 | }
65 | } catch (e) {
66 | console.error(e);
67 | }
68 | }
69 |
70 | /**
71 | * Delete all server from list
72 | */
73 | export const resetServer = async () => {
74 | try {
75 | return await AsyncStorage.setItem('servers', JSON.stringify([]));
76 | } catch (e) {
77 | console.error(e)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/functions/SingleAccountUtilities.jsx:
--------------------------------------------------------------------------------
1 | export const getCountryFlag = (countryCode) => {
2 | let url;
3 |
4 | switch (countryCode) {
5 | case 'ko':
6 | url = `https://flagcdn.com/16x12/kr.png`;
7 | break;
8 | default:
9 | url = `https://flagcdn.com/16x12/${countryCode}.png`;
10 | break;
11 | }
12 |
13 | return url;
14 | };
15 |
--------------------------------------------------------------------------------
/src/screens/ActiveSessions.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { ScrollView, RefreshControl, Alert, StyleSheet } from 'react-native';
4 | import { Card, ListItem, Avatar } from '@rneui/themed';
5 | import Spinner from 'react-native-loading-spinner-overlay';
6 | import { sessionTitle } from '../functions/ServerManageUtiles';
7 | import { useNavigation } from '@react-navigation/native';
8 |
9 | class ActiveSessions extends React.Component {
10 | localStyle = StyleSheet.create({
11 | manageContainer: {
12 | flex: 1
13 | },
14 | serverIdLabel: {
15 | fontWeight: 'bold'
16 | },
17 | serverIdValue: {
18 | color: '#000000'
19 | },
20 | item: {
21 | color: '#000',
22 | width: '33%'
23 | },
24 | accordionTitle: {
25 | fontWeight: 'bold',
26 | fontSize: 14
27 | },
28 | updateAllView: {
29 | width: '100%'
30 | }
31 | });
32 |
33 | constructor(props) {
34 | super(props);
35 |
36 | this.state = {
37 | sessions: this.props.route.params.sessions,
38 | activeSessionsList: true,
39 | refreshing: false,
40 | server: this.props.route.params.server
41 | };
42 |
43 | this.refresh = this.refresh.bind(this);
44 | }
45 |
46 | async fetchActiveSessions() {
47 | try {
48 | let activeSessions = await axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/status/sessions?X-Plex-Token=${this.state.server.token}`);
49 |
50 | this.setState({ sessions: activeSessions.data.MediaContainer.Metadata });
51 | } catch (e) {
52 | Alert.alert('Error', 'Something went wrong during active sessions fetch!');
53 | }
54 | }
55 |
56 | refresh() {
57 | this.setState({ refreshing: true });
58 |
59 | this.fetchActiveSessions()
60 | .finally(() => {
61 | this.setState({ refreshing: false });
62 | });
63 | }
64 |
65 | render() {
66 | return (
67 |
70 | }
71 | >
72 |
73 | <>
74 |
77 | Actives Sessions
78 |
79 | }
80 | isExpanded={this.state.activeSessionsList}
81 | onPress={() => {
82 | this.setState({ activeSessionsList: !this.state.activeSessionsList });
83 | }}
84 | >
85 | {!this.state.sessions ? (
86 |
87 |
88 | No active sessions
89 |
90 |
91 | ) : (
92 | this.state.sessions.map((session, index) => {
93 | return (
94 | {
98 | this.props.navigation.navigate('SingleSession', {
99 | title: session.Session.id,
100 | server: this.state.server,
101 | session: session
102 | })
103 | }}
104 | >
105 |
109 |
110 |
111 | {sessionTitle(session)}
112 | {session.Player.state}
113 | {session.Player.address}
114 | {session.Player.product}
115 | {session.Player.version}
116 |
117 |
118 |
119 |
120 | );
121 | })
122 | )}
123 |
124 | >
125 |
126 |
127 | );
128 | }
129 | }
130 |
131 | export default (props) => {
132 | const navigation = useNavigation();
133 | return ;
134 | };
135 |
--------------------------------------------------------------------------------
/src/screens/Activities.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, RefreshControl, ScrollView, StyleSheet } from 'react-native';
3 | import { Card, ListItem } from "@rneui/themed";
4 | import axios from 'axios';
5 | import { useNavigation } from '@react-navigation/native';
6 |
7 | class Activities extends React.Component {
8 | localStyle = StyleSheet.create({
9 | manageContainer: {
10 | flex: 1
11 | },
12 | serverIdLabel: {
13 | fontWeight: 'bold'
14 | },
15 | serverIdValue: {
16 | color: '#000000'
17 | },
18 | item: {
19 | color: '#000',
20 | width: '33%'
21 | },
22 | accordionTitle: {
23 | fontWeight: 'bold',
24 | fontSize: 14
25 | },
26 | updateAllView: {
27 | width: '100%'
28 | }
29 | });
30 |
31 | constructor(props) {
32 | super(props);
33 |
34 | this.state = {
35 | activities: this.props.route.params.activities,
36 | server: this.props.route.params.server,
37 | activitiesList: true,
38 | refreshing: false
39 | };
40 |
41 | this.refresh = this.refresh.bind(this);
42 | }
43 |
44 | async fetchActivities() {
45 | try {
46 | const activities = await axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/activities?X-Plex-Token=${this.state.server.token}`);
47 |
48 | this.setState({ activities: activities.data.MediaContainer });
49 | } catch (e) {
50 | Alert.alert('Error', 'Somenthing went wront during activities fetch!');
51 | }
52 | }
53 |
54 | refresh() {
55 | this.setState({ refreshing: true });
56 |
57 | this.fetchActivities()
58 | .finally(() => {
59 | this.setState({ refreshing: false });
60 | });
61 | }
62 |
63 | render() {
64 | return (
65 |
68 | }
69 | >
70 |
71 |
74 | Activities
75 |
76 | }
77 | isExpanded={this.state.activitiesList}
78 | onPress={() => {
79 | this.setState({ activitiesList: !this.state.activitiesList });
80 | }}
81 | >
82 | {this.state.activities.size === 0 ? (
83 |
84 |
85 | No activities
86 |
87 |
88 | ) : (
89 | this.state.activities.Activity.map((activity, index) => {
90 | return (
91 | {
95 | Alert.alert('Confirmation', 'Are you sure you want to cancel this activity ?', [
96 | {
97 | text: 'Yes',
98 | style: 'destructive',
99 | onPress: async () => {
100 | try {
101 | await axios.delete(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/activities/${activity.uuid}?X-Plex-Token=${this.state.server.token}`);
102 |
103 | Alert.alert('Success', 'The activity has been successfully canceled!');
104 | } catch (e) {
105 | Alert.alert('Error', 'Something went wrong during the cancelation of the activity! May be the activity is already done.');
106 | }
107 | }
108 | }, {
109 | text: 'No',
110 | style: 'cancel'
111 | }
112 | ]);
113 | }}
114 | >
115 |
116 | {activity.title}
117 | Progress: {activity.progress}%
118 | Type: {activity.type}
119 | UUID: {activity.uuid}
120 | Cancellable: {activity.cancellable.toString()}
121 |
122 |
123 |
124 |
125 | );
126 | })
127 | )}
128 |
129 |
130 |
131 | );
132 | }
133 | }
134 |
135 | export default (props) => {
136 | const navigation = useNavigation();
137 | return ;
138 | };
139 |
--------------------------------------------------------------------------------
/src/screens/EditServer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Text,
4 | TextInput,
5 | Alert,
6 | Linking, StyleSheet, View,
7 | } from 'react-native';
8 | import { Button } from '@rneui/themed';
9 | import Icon from 'react-native-vector-icons/FontAwesome5';
10 | import { Picker } from '@react-native-picker/picker';
11 | import { editServer } from '../functions/ServerStorage';
12 | import { useNavigation } from '@react-navigation/native';
13 | import { Formik } from 'formik';
14 | import Colors from '../utiles/Colors';
15 |
16 | class EditServer extends React.Component {
17 | protocolOptions = [
18 | { label: 'HTTP', value: 'http' },
19 | { label: 'HTTPS', value: 'https' }
20 | ];
21 |
22 | serverTypeOptions = [
23 | { label: 'Computer', value: 'computer' },
24 | { label: 'NAS', value: 'nas' }
25 | ];
26 |
27 | localStyle = StyleSheet.create({
28 | input: {
29 | color: Colors.White,
30 | backgroundColor: Colors.PlexBlack,
31 | height: 55,
32 | borderColor: Colors.White,
33 | borderWidth: 1,
34 | marginBottom: 10,
35 | paddingLeft: 17.5,
36 | paddingRight: 17.5,
37 | fontSize: 15,
38 | },
39 | picker: {
40 | color: Colors.White,
41 | backgroundColor: Colors.PlexBlack,
42 | borderColor: Colors.White,
43 | borderWidth: 1,
44 | marginBottom: 10,
45 | paddingLeft: 10,
46 | color: Colors.White
47 | },
48 | required: {
49 | color: Colors.PlexYellow,
50 | fontWeight: 'bold',
51 | fontSize: 15
52 | },
53 | sendBtn: {
54 | marginRight: 10
55 | },
56 | helpLink: {
57 | textDecorationLine: 'underline',
58 | marginBottom: 10,
59 | color: Colors.White
60 | }
61 | });
62 |
63 | constructor(props) {
64 | super(props);
65 |
66 | this.state = {
67 | protocol: this.props.route.params.server.protocol,
68 | serverType: this.props.route.params.server.serverType,
69 | name: this.props.route.params.server.name,
70 | ip: this.props.route.params.server.ip,
71 | port: this.props.route.params.server.port,
72 | token: this.props.route.params.server.token,
73 | index: undefined
74 | }
75 |
76 | this.editServer = this.editServer.bind(this);
77 | }
78 |
79 | componentDidMount() {
80 | this.setState({ index: this.props.route.params.index });
81 | }
82 |
83 | async editServer(values, formikBag) {
84 | try {
85 | await editServer(values, this.state.index);
86 |
87 | formikBag.resetForm();
88 |
89 | Alert.alert('Success', 'Server was correctly edited!', [
90 | {
91 | text: 'OK',
92 | onPress: () => {
93 | this.props.navigation.navigate('Server');
94 | }
95 | }
96 | ]);
97 | } catch (e) {
98 | Alert.alert(
99 | 'Error',
100 | `Something went wrong!\nIf the error persist leave a issue on my repo.\n\nhttps://github.com/sikelio/plexmanager/issues`
101 | );
102 | }
103 | }
104 |
105 | validate(values) {
106 | const ipDomainRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:(?!-)[A-Za-z0-9-]{1,63}(? 65535) {
134 | errors.port = 'Entered port is invalid!\nThe port range is between 0 and 65535.';
135 | }
136 |
137 | return errors;
138 | }
139 |
140 | render() {
141 | return (
142 |
147 | {({ handleChange, handleBlur, handleSubmit, values, errors, resetForm }) => (
148 |
155 |
158 |
165 | {this.protocolOptions.map(option => (
166 |
167 | ))}
168 |
169 |
170 |
171 |
174 |
181 | {this.serverTypeOptions.map(option => (
182 |
183 | ))}
184 |
185 |
186 |
187 |
188 | {errors.name && {errors.name}}
189 |
197 |
198 |
199 |
200 | {errors.ip && {errors.ip}}
201 |
209 |
210 |
211 |
212 | {errors.port && {errors.port}}
213 |
222 |
223 |
224 |
225 | {errors.token && {errors.token}}
226 |
235 |
236 |
241 | Linking.openURL('https://github.com/sikelio/plexmanager/wiki/Getting-Plex-Media-Server-Access-Token')}
243 | style={this.localStyle.helpLink}
244 | >
245 | How to I get my Plex Token ?
246 |
247 |
248 |
249 |
250 |
262 | }
263 | />
264 |
265 | )}
266 |
267 | );
268 | }
269 | }
270 |
271 | export default (props) => {
272 | const navigation = useNavigation();
273 | return ;
274 | };
275 |
--------------------------------------------------------------------------------
/src/screens/NewServer.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Text,
3 | TextInput,
4 | Alert,
5 | Linking, View, StyleSheet,
6 | } from "react-native";
7 | import { Button } from '@rneui/themed';
8 | import Icon from 'react-native-vector-icons/FontAwesome5';
9 | import { Picker } from '@react-native-picker/picker';
10 | import React from 'react';
11 | import { Formik } from 'formik';
12 | import { storeServer } from '../functions/ServerStorage';
13 | import Colors from '../utiles/Colors';
14 |
15 | export default class NewServer extends React.Component {
16 | protocolOptions = [
17 | { label: 'HTTP', value: 'http' },
18 | { label: 'HTTPS', value: 'https' }
19 | ];
20 |
21 | serverTypeOptions = [
22 | { label: 'Computer', value: 'computer' },
23 | { label: 'NAS', value: 'nas' }
24 | ];
25 |
26 | localStyle = StyleSheet.create({
27 | input: {
28 | color: Colors.White,
29 | backgroundColor: Colors.PlexBlack,
30 | height: 55,
31 | borderColor: Colors.White,
32 | borderWidth: 1,
33 | marginBottom: 10,
34 | paddingLeft: 17.5,
35 | paddingRight: 17.5,
36 | fontSize: 15,
37 | },
38 | picker: {
39 | color: Colors.White,
40 | backgroundColor: Colors.PlexBlack,
41 | borderColor: Colors.White,
42 | borderWidth: 1,
43 | marginBottom: 10,
44 | paddingLeft: 10,
45 | color: Colors.White
46 | },
47 | required: {
48 | color: Colors.PlexYellow,
49 | fontWeight: 'bold',
50 | fontSize: 15
51 | },
52 | sendBtn: {
53 | marginRight: 10
54 | },
55 | helpLink: {
56 | textDecorationLine: 'underline',
57 | marginBottom: 10,
58 | color: Colors.White
59 | }
60 | });
61 |
62 | constructor() {
63 | super();
64 |
65 | this.state = {
66 | protocol: 'http',
67 | serverType: 'computer',
68 | name: '',
69 | ip: '',
70 | port: '32400',
71 | token: ''
72 | }
73 | }
74 |
75 | async saveServer(values, formikBag) {
76 | try {
77 | await storeServer(values);
78 |
79 | formikBag.resetForm();
80 |
81 | Alert.alert('Success', 'Server was correctly added!');
82 | } catch (e) {
83 | Alert.alert(
84 | 'Error',
85 | `Something went wrong!\nIf the error persist leave a issue on my repo.\n\nhttps://github.com/sikelio/plexmanager/issues`
86 | );
87 | }
88 | }
89 |
90 | validate(values) {
91 | const ipDomainRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:(?!-)[A-Za-z0-9-]{1,63}(? 65535) {
119 | errors.port = 'Entered port is invalid!\nThe port range is between 0 and 65535.';
120 | }
121 |
122 | return errors;
123 | }
124 |
125 | render() {
126 | return (
127 |
132 | {({ handleChange, handleBlur, handleSubmit, values, errors, resetForm }) => (
133 |
140 |
143 |
150 | {this.protocolOptions.map(option => (
151 |
152 | ))}
153 |
154 |
155 |
156 |
159 |
166 | {this.serverTypeOptions.map(option => (
167 |
168 | ))}
169 |
170 |
171 |
172 |
173 | {errors.name && {errors.name}}
174 |
182 |
183 |
184 |
185 | {errors.ip && {errors.ip}}
186 |
194 |
195 |
196 |
197 | {errors.port && {errors.port}}
198 |
207 |
208 |
209 |
210 | {errors.token && {errors.token}}
211 |
220 |
221 |
226 | Linking.openURL('https://github.com/sikelio/plexmanager/wiki/Getting-Plex-Media-Server-Access-Token')}
228 | style={this.localStyle.helpLink}
229 | >
230 | How to I get my Plex Token ?
231 |
232 |
233 |
234 |
235 |
247 | }
248 | />
249 |
250 | )}
251 |
252 | );
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/src/screens/NewestMovies.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, RefreshControl, ScrollView, StyleSheet, Text, View } from "react-native";
3 | import { useNavigation } from '@react-navigation/native';
4 | import { Avatar, Card, ListItem } from "@rneui/themed";
5 | import axios from "axios";
6 | import FastImage from "react-native-fast-image";
7 |
8 | class NewestMovies extends React.Component {
9 | localStyle = StyleSheet.create({
10 | libraryContainer: {
11 | flex: 1
12 | },
13 | container: {
14 | flexDirection: 'row',
15 | justifyContent: 'space-between',
16 | width: '100%'
17 | },
18 | upperContainer: {
19 | marginBottom: 10
20 | },
21 | bottomContainer: {
22 | marginTop: 20
23 | }
24 | });
25 |
26 | constructor(props) {
27 | super(props);
28 |
29 | this.state = {
30 | movies: this.props.route.params.movies,
31 | newestMoviesList: true,
32 | server: this.props.route.params.server,
33 | refreshing: false,
34 | libraryKey: this.props.route.params.libraryKey
35 | };
36 |
37 | this.refresh = this.refresh.bind(this);
38 | }
39 |
40 | async fetchNewestMovies() {
41 | try {
42 | const [
43 | updatedNewestMovies
44 | ] = await Promise.all([
45 | axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/library/sections/${this.state.libraryKey}/newest?X-Plex-Token=${this.state.server.token}`)
46 | ]);
47 |
48 | this.setState({ movies: updatedNewestMovies.data.MediaContainer.Metadata });
49 | } catch (e) {
50 | Alert.alert('Error', 'Something went wrong during the library fetch!');
51 | }
52 | }
53 |
54 | refresh() {
55 | this.setState({ refreshing: true });
56 |
57 | this.fetchNewestMovies()
58 | .finally(() => {
59 | this.setState({ refreshing: false });
60 | });
61 | }
62 |
63 | render() {
64 | return (
65 |
68 | }
69 | >
70 |
71 |
74 | Newest movies
75 |
76 | }
77 | isExpanded={this.state.newestMoviesList}
78 | onPress={() => {
79 | this.setState({ newestMoviesList: !this.state.newestMoviesList });
80 | }}
81 | >
82 | {this.state.movies.map((movie, index) => {
83 | return (
84 |
88 | (
90 |
102 | )}
103 | overlayContainerStyle={{
104 | justifyContent: 'center',
105 | alignItems: 'center',
106 | }}
107 | />
108 |
109 |
110 | {movie.title}
111 | {movie.studio}
112 |
113 |
114 | );
115 | })}
116 |
117 |
118 |
119 | );
120 | }
121 | }
122 |
123 | export default (props) => {
124 | const navigation = useNavigation();
125 | return ;
126 | };
127 |
--------------------------------------------------------------------------------
/src/screens/ScheduledTasks.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text, ScrollView, View, StyleSheet } from 'react-native';
3 | import { Card } from '@rneui/themed';
4 | import { useNavigation } from "@react-navigation/native";
5 |
6 | class ScheduledTasks extends React.Component {
7 | localStyle = StyleSheet.create({
8 | textColor: {
9 | color: '#000'
10 | },
11 | textLabel: {
12 | fontWeight: 'bold'
13 | }
14 | });
15 |
16 | constructor(props) {
17 | super(props);
18 |
19 | this.state = {
20 | scheduledTasks: this.props.route.params.scheduledTasks
21 | };
22 | }
23 |
24 | render() {
25 | return (
26 |
27 | {this.state.scheduledTasks.map((task, index) => {
28 | return (
29 |
32 | {task.name}
33 |
34 |
35 |
36 | Description :
37 | {task.description ? task.description : 'none'}
38 |
39 |
40 | Schedule randomized :
41 | {task.scheduleRandomized.toString()}
42 |
43 |
44 | Interval :
45 | {task.interval}
46 |
47 |
48 |
49 | );
50 | })}
51 |
52 | );
53 | }
54 | }
55 |
56 | export default (props) => {
57 | const navigation = useNavigation();
58 | return ;
59 | };
60 |
--------------------------------------------------------------------------------
/src/screens/ServerCapabilities.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
3 | import { useNavigation } from '@react-navigation/native';
4 | import { Card } from '@rneui/themed';
5 | import axios from 'axios';
6 | import Colors from '../utiles/Colors';
7 |
8 | class ServerCapabilities extends React.Component {
9 | localStyle = StyleSheet.create({
10 | cardBodyText: {
11 | color: Colors.White
12 | }
13 | });
14 |
15 | constructor(props) {
16 | super(props);
17 |
18 | this.state = {
19 | serverCapabilities: this.props.route.params.serverCapabilities,
20 | server: this.props.route.params.server,
21 | refresh: false,
22 | };
23 |
24 | this.refresh = this.refresh.bind(this);
25 | }
26 |
27 | async fetchServerCapabilities() {
28 | try {
29 | let serverCapabilities = await axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/?X-Plex-Token=${this.state.server.token}`);
30 |
31 | this.setState({ serverCapabilities: serverCapabilities.data.MediaContainer });
32 | } catch (e) {
33 | Alert.alert('Error', 'Something went wrong while server capabilities fetch!');
34 | }
35 | }
36 |
37 | refresh() {
38 | this.setState({ refreshing: true });
39 |
40 | this.fetchServerCapabilities()
41 | .finally(() => {
42 | this.setState({ refreshing: false });
43 | });
44 | }
45 |
46 | render() {
47 | return (
48 |
51 | }
52 | style={{
53 | backgroundColor: Colors.PlexGrey
54 | }}
55 | >
56 | {Object.keys(this.state.serverCapabilities).map((key, index) => {
57 | if (key === 'Directory') {
58 | return (
59 |
67 |
72 | {key}
73 |
74 |
75 |
76 |
77 |
78 | {this.state.serverCapabilities[key].map((folder, folderIndex) => {
79 | return (
80 |
84 | {folder.title}: Count {folder.count}
85 |
86 | );
87 | })}
88 |
89 |
90 | );
91 | }
92 |
93 | if (this.state.serverCapabilities[key] == true || this.state.serverCapabilities[key] == false) {
94 | return (
95 |
103 |
108 | {key}
109 |
110 |
111 |
112 |
113 |
114 |
117 | {this.state.serverCapabilities[key].toString()}
118 |
119 |
120 |
121 | );
122 | }
123 |
124 | return (
125 |
133 |
138 | {key}
139 |
140 |
141 |
142 |
143 |
144 |
147 | {this.state.serverCapabilities[key]}
148 |
149 |
150 |
151 | );
152 | })}
153 |
154 | );
155 | }
156 | }
157 |
158 | export default (props) => {
159 | const navigation = useNavigation();
160 | return
161 | }
162 |
--------------------------------------------------------------------------------
/src/screens/ServerPreferences.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text, ScrollView, View, StyleSheet } from 'react-native';
3 | import { Card } from '@rneui/themed';
4 | import {
5 | getPreferenceGroupName,
6 | getPreferenceLabel,
7 | getPreferenceSummary,
8 | getPreferenceValue
9 | } from '../functions/ServerManageUtiles';
10 | import { useNavigation } from '@react-navigation/native';
11 | import Colors from '../utiles/Colors';
12 |
13 | class ServerPreferences extends React.Component {
14 | localStyle = StyleSheet.create({
15 | cardBodyText: {
16 | color: Colors.White
17 | }
18 | });
19 |
20 | constructor(props) {
21 | super(props)
22 |
23 | this.state = {
24 | preferences: this.props.route.params.preferences
25 | };
26 | }
27 |
28 | render() {
29 | return (
30 |
35 | {this.state.preferences.map((preference, index) => {
36 | return (
37 |
45 |
50 | {getPreferenceLabel(preference)}
51 |
52 |
53 |
54 |
55 |
56 |
59 |
64 | Summary:
65 | {getPreferenceSummary(preference)}
66 |
67 |
70 |
75 | Group:
76 | {getPreferenceGroupName(preference)}
77 |
78 |
81 |
86 | Value:
87 | {getPreferenceValue(preference)}
88 |
89 |
92 |
97 | Advanced:
98 | {preference.advanced.toString()}
99 |
100 |
103 |
108 | Hidden:
109 | {preference.hidden.toString()}
110 |
111 |
112 |
113 | );
114 | })}
115 |
116 | );
117 | }
118 | }
119 |
120 | export default (props) => {
121 | const navigation = useNavigation();
122 | return ;
123 | };
124 |
--------------------------------------------------------------------------------
/src/screens/Settings.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, View } from "react-native";
3 | import { Text, Card, Switch } from '@rneui/themed';
4 | import { saveDarkmode } from "../functions/Darkmode";
5 |
6 | class Settings extends React.Component {
7 | localStyle = StyleSheet.create({
8 | container: {
9 | flexDirection: 'row',
10 | flexWrap: 'wrap',
11 | alignItems: 'flex-start',
12 | marginVertical: 2.5
13 | },
14 | item: {
15 | width: '85%',
16 | fontSize: 18
17 | }
18 | });
19 |
20 | constructor(props) {
21 | super(props);
22 |
23 | this.state = {
24 | darkmode: false
25 | };
26 | }
27 |
28 | render() {
29 | return (
30 |
31 |
32 |
35 |
38 | Darkmode
39 |
40 |
41 | {
44 | this.setState({ darkmode: value });
45 |
46 | await saveDarkmode(value);
47 | }}
48 | />
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default Settings;
57 |
--------------------------------------------------------------------------------
/src/screens/SingleAccount.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScrollView, StyleSheet, Text, View } from 'react-native';
3 | import { Card } from '@rneui/themed';
4 | import FastImage from 'react-native-fast-image';
5 | import { getCountryFlag } from '../functions/SingleAccountUtilities';
6 | import { useNavigation } from '@react-navigation/native';
7 | import Colors from '../utiles/Colors';
8 |
9 | class SingleAccount extends React.Component {
10 | localStyle = StyleSheet.create({
11 | textColor: {
12 | color: Colors.White,
13 | marginBottom: 10
14 | },
15 | textLabel: {
16 | fontWeight: 'bold'
17 | }
18 | });
19 |
20 | constructor(props) {
21 | super(props);
22 |
23 | this.state = {
24 | userDetails: this.props.route.params.userDetails
25 | };
26 | }
27 |
28 | render() {
29 | return (
30 |
35 |
42 |
47 | {this.state.userDetails.name}
48 |
49 |
50 |
51 |
52 |
53 |
54 | Auto select audio:
55 | {this.state.userDetails.autoSelectAudio.toString()}
56 |
57 |
58 | Default audio language:
59 |
69 |
70 |
71 | Default subtitle language:
72 |
82 |
83 |
84 | Subtitle mode:
85 | {this.state.userDetails.subtitleMode}
86 |
87 |
88 |
89 |
90 | );
91 | }
92 | }
93 |
94 | export default (props) => {
95 | const navigation = useNavigation();
96 | return ;
97 | };
98 |
--------------------------------------------------------------------------------
/src/screens/SingleEpisode.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Dimensions, ScrollView, StyleSheet, Text, View } from 'react-native';
3 | import { Card } from '@rneui/themed';
4 | import FastImage from 'react-native-fast-image';
5 | import { useNavigation } from '@react-navigation/native';
6 | import Colors from '../utiles/Colors';
7 |
8 | class SingleEpisode extends React.Component {
9 | mainImgWidth = Dimensions.get('window').width;
10 | mainImgHeight = (Dimensions.get('window').width * 9 / 16);
11 |
12 | localStyle = StyleSheet.create({
13 | textColor: {
14 | color: Colors.White
15 | },
16 | textLabel: {
17 | fontWeight: 'bold'
18 | }
19 | });
20 |
21 | constructor(props) {
22 | super(props);
23 |
24 | this.state = {
25 | server: this.props.route.params.server,
26 | episode: this.props.route.params.episode
27 | };
28 | }
29 |
30 | render() {
31 | return (
32 |
38 |
45 |
46 |
57 |
58 |
59 |
67 | {this.state.episode.title}
68 |
69 |
70 |
71 |
72 |
73 | Summary:
74 | {this.state.episode.summary}
75 |
76 |
77 |
78 | Duration:
79 | {this.state.episode.duration ? new Date(this.state.episode.duration).toISOString().slice(11, 19) : 'unknown'}
80 |
81 |
82 |
83 | );
84 | }
85 | }
86 |
87 | export default (props) => {
88 | const navigation = useNavigation();
89 | return ;
90 | };
91 |
--------------------------------------------------------------------------------
/src/screens/SingleLibrary.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { Alert, RefreshControl, ScrollView, StyleSheet, View, Text } from 'react-native';
4 | import { Card, Button, ListItem, Avatar } from '@rneui/themed';
5 | import FastImage from 'react-native-fast-image';
6 | import Spinner from 'react-native-loading-spinner-overlay';
7 | import { sendPutRequest, sendRequest } from '../functions/ServerRequest';
8 | import { useNavigation } from '@react-navigation/native';
9 | import Icon from 'react-native-vector-icons/FontAwesome5';
10 | import Colors from '../utiles/Colors';
11 |
12 | class SingleLibrary extends React.Component {
13 | localStyle = StyleSheet.create({
14 | libraryContainer: {
15 | flex: 1
16 | },
17 | container: {
18 | flexDirection: 'row',
19 | justifyContent: 'space-between',
20 | width: '100%'
21 | },
22 | upperContainer: {
23 | marginBottom: 10
24 | },
25 | bottomContainer: {
26 | marginTop: 10
27 | },
28 | accordionTitle: {
29 | fontWeight: 'bold',
30 | fontSize: 14,
31 | color: Colors.PlexYellow
32 | }
33 | });
34 |
35 | constructor(props) {
36 | super(props);
37 |
38 | this.state = {
39 | library: this.props.route.params.library,
40 | server: this.props.route.params.server,
41 | mediaList: true,
42 | spinner: false,
43 | refreshing: false,
44 | medias: this.props.route.params.medias
45 | };
46 |
47 | this.refresh = this.refresh.bind(this);
48 | }
49 |
50 | async updateLibrary() {
51 | try {
52 | const [
53 | updatedMedias
54 | ] = await Promise.all([
55 | axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/library/sections/${this.state.library.key}/all?X-Plex-Token=${this.state.server.token}`)
56 | ]);
57 |
58 | this.setState({ medias: updatedMedias.data.MediaContainer.Metadata });
59 | } catch (e) {
60 | Alert.alert('Error', 'Something went wrong during the library fetch!');
61 | }
62 | }
63 |
64 | refresh() {
65 | this.setState({ refreshing: true });
66 |
67 | this.updateLibrary()
68 | .finally(() => {
69 | this.setState({ refreshing: false });
70 | });
71 | }
72 |
73 | render() {
74 | return (
75 |
76 |
84 |
85 |
88 | }
89 | style={{
90 | backgroundColor: Colors.PlexGrey
91 | }}
92 | >
93 |
100 |
105 | Actions
106 |
107 |
108 |
109 |
110 |
111 |
137 |
138 |
139 | {
148 | sendPutRequest(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/library/sections/${this.state.library.key}/emptyTrash?X-Plex-Token=${this.state.server.token}`);
149 | }}
150 | />
151 |
152 |
153 | {this.state.library.type === 'movie' ? (
154 |
155 | {
164 | try {
165 | this.setState({ spinner: true });
166 |
167 | let newestMovies = await axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/library/sections/${this.state.library.key}/newest?X-Plex-Token=${this.state.server.token}`);
168 |
169 | this.props.navigation.navigate('NewestMovies', {
170 | title: `Newest Movies - ${this.state.library.title}`,
171 | movies: newestMovies.data.MediaContainer.Metadata,
172 | server: this.state.server,
173 | libraryKey: this.state.library.key
174 | });
175 |
176 | this.setState({ spinner: false });
177 | } catch (e) {
178 | Alert.alert('Error', 'Something went wrong during newest movies fetch!');
179 | }
180 | }}
181 | />
182 |
183 | ) : (
184 |
185 | )}
186 |
187 |
188 |
195 |
198 | Medias
199 |
200 | }
201 | isExpanded={this.state.mediaList}
202 | onPress={() => {
203 | this.setState({ mediaList: !this.state.mediaList });
204 | }}
205 | containerStyle={{
206 | backgroundColor: Colors.PlexBlack
207 | }}
208 | icon={}
209 | expandIcon={}
210 | >
211 | {this.state.medias.map((media, index) => {
212 | return (
213 | {
216 | this.setState({ spinner: true });
217 |
218 | if (this.state.library.type !== 'show') {
219 |
220 | return this.props.navigation.navigate('SingleMedia', {
221 | title: media.title,
222 | library: this.state.library,
223 | media: media,
224 | server: this.state.server
225 | });
226 | }
227 |
228 | try {
229 | let seasons = await axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}${media.key}?X-Plex-Token=${this.state.server.token}`);
230 |
231 | this.props.navigation.navigate('SingleMedia', {
232 | title: media.title,
233 | library: this.state.library,
234 | media: media,
235 | server: this.state.server,
236 | seasons: seasons.data.MediaContainer.Metadata
237 | });
238 |
239 | this.setState({ spinner: false });
240 | } catch (e) {
241 | Alert.alert('Error', 'Something went wrong while fetching the TV show seasons!');
242 | }
243 | }}
244 | containerStyle={{
245 | backgroundColor: Colors.PlexBlack
246 | }}
247 | >
248 | (
250 |
262 | )}
263 | overlayContainerStyle={{
264 | justifyContent: 'center',
265 | alignItems: 'center',
266 | }}
267 | />
268 |
269 |
270 |
275 | {media.title}
276 |
277 |
278 |
283 | {media.studio}
284 |
285 |
286 |
287 |
288 |
289 | );
290 | })}
291 |
292 |
293 |
294 |
295 | );
296 | }
297 | }
298 |
299 | export default (props) => {
300 | const navigation = useNavigation();
301 | return ;
302 | };
303 |
--------------------------------------------------------------------------------
/src/screens/SingleMedia.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { Alert, Dimensions, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
4 | import { Avatar, Card, ListItem } from '@rneui/themed';
5 | import FastImage from 'react-native-fast-image';
6 | import Spinner from 'react-native-loading-spinner-overlay';
7 | import { getDateFromTimestamp, getTimeFromTimestamp } from '../functions/GlobalUtiles';
8 | import Icon from 'react-native-vector-icons/FontAwesome5';
9 | import { useNavigation } from '@react-navigation/native';
10 | import Colors from '../utiles/Colors';
11 |
12 | class SingleMedia extends React.Component {
13 | mainImgWidth = Dimensions.get('window').width;
14 | mainImgHeight = (Dimensions.get('window').width * 9 / 16);
15 |
16 | localStyle = StyleSheet.create({
17 | textColor: {
18 | color: Colors.White
19 | },
20 | textLabel: {
21 | fontWeight: 'bold'
22 | },
23 | accordionTitle: {
24 | fontWeight: 'bold',
25 | fontSize: 14,
26 | color: Colors.PlexYellow
27 | }
28 | });
29 |
30 | constructor(props) {
31 | super(props);
32 |
33 | this.state = {
34 | library: this.props.route.params.library,
35 | server: this.props.route.params.server,
36 | seasonsList: true,
37 | spinner: false,
38 | refreshing: false,
39 | media: this.props.route.params.media,
40 | seasons: this.props.route.params.seasons
41 | };
42 |
43 | this.refresh = this.refresh.bind(this);
44 | }
45 |
46 | async updateMedia () {
47 | try {
48 | const [
49 | updatedSeasons
50 | ] = await Promise.all([
51 | axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}${this.state.media.key}?X-Plex-Token=${this.state.server.token}`)
52 | ]);
53 |
54 | this.setState({ seasons: updatedSeasons.data.MediaContainer.Metadata });
55 | } catch (e) {
56 | Alert.alert('Error', 'Something went wrong during the seasons fetch!');
57 | }
58 | };
59 |
60 | refresh() {
61 | this.setState({ refreshing: true });
62 |
63 | this.updateMedia()
64 | .finally(() => {
65 | this.setState({ refreshing: false });
66 | });
67 | }
68 |
69 | render() {
70 | return (
71 |
76 |
84 |
85 |
88 | }
89 | style={{
90 | backgroundColor: Colors.PlexGrey
91 | }}
92 | >
93 |
100 |
101 |
112 |
113 |
121 | {this.state.media.title} {this.state.media.studio ? `- ${this.state.media.studio}` : ''}
122 |
123 |
124 |
125 |
126 |
127 | Summary:
128 | {this.state.media.summary}
129 |
130 |
131 |
132 | Duration:
133 | {this.state.media.duration ? new Date(this.state.media.duration).toISOString().slice(11, 19) : 'unknown'}
134 |
135 |
136 |
137 | Rating:
138 | {this.state.media.rating ? this.state.media.rating : 'unknown'}
139 |
140 |
141 |
142 | Added at:
143 | {getDateFromTimestamp(this.state.media.addedAt)} - {getTimeFromTimestamp(this.state.media.addedAt)}
144 |
145 |
146 |
147 |
148 | {this.state.library.type === 'show' ? (
149 |
156 |
159 | Seasons
160 |
161 | }
162 | isExpanded={this.state.seasonsList}
163 | onPress={() => {
164 | this.setState({ seasonsList: !this.state.seasonsList });
165 | }}
166 | containerStyle={{
167 | backgroundColor: Colors.PlexBlack
168 | }}
169 | icon={}
170 | expandIcon={}
171 | >
172 | {this.state.seasons.map((season, index) => {
173 | return (
174 | {
177 | try {
178 | this.setState({ spinner: true });
179 |
180 | let episodes = await axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}${season.key}?X-Plex-Token=${this.state.server.token}`);
181 |
182 | this.props.navigation.navigate('SingleSeason', {
183 | title: `${this.state.media.title} - ${season.title}`,
184 | season: season,
185 | episodes: episodes.data.MediaContainer.Metadata,
186 | server: this.state.server,
187 | media: this.state.media
188 | });
189 |
190 | this.setState({ spinner: false });
191 | } catch (e) {
192 | Alert.alert('Error', 'Something went wrong during episodes fetch!');
193 | }
194 | }}
195 | containerStyle={{
196 | backgroundColor: Colors.PlexBlack
197 | }}
198 | >
199 | (
201 |
213 | )}
214 | overlayContainerStyle={{
215 | justifyContent: 'center',
216 | alignItems: 'center',
217 | }}
218 | />
219 |
220 |
221 |
226 | {season.title}
227 |
228 |
229 |
234 | {season.year ? season.year : season.parentYear}
235 |
236 |
237 |
238 |
239 | )
240 | })}
241 |
242 |
243 | ) : (
244 |
245 | )}
246 |
247 |
248 | );
249 | }
250 | }
251 |
252 | export default (props) => {
253 | const navigation = useNavigation();
254 | return ;
255 | };
256 |
--------------------------------------------------------------------------------
/src/screens/SingleSeason.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { Alert, Dimensions, RefreshControl, ScrollView, StyleSheet, Text, View } from 'react-native';
4 | import { Avatar, Card, ListItem } from '@rneui/themed';
5 | import FastImage from 'react-native-fast-image';
6 | import Spinner from 'react-native-loading-spinner-overlay';
7 | import { useNavigation } from '@react-navigation/native';
8 | import Icon from 'react-native-vector-icons/FontAwesome5';
9 | import Colors from '../utiles/Colors';
10 |
11 | class SingleSeason extends React.Component {
12 | mainImgWidth = Dimensions.get('window').width;
13 | mainImgHeight = (Dimensions.get('window').width * 9 / 16);
14 |
15 | localStyle = StyleSheet.create({
16 | textColor: {
17 | color: Colors.White
18 | },
19 | textLabel: {
20 | fontWeight: 'bold'
21 | },
22 | accordionTitle: {
23 | fontWeight: 'bold',
24 | fontSize: 14,
25 | color: Colors.PlexYellow
26 | }
27 | });
28 |
29 | constructor(props) {
30 | super(props);
31 |
32 | this.state = {
33 | season: this.props.route.params.season,
34 | server: this.props.route.params.server,
35 | media: this.props.route.params.media,
36 | episodesList: true,
37 | spinner: false,
38 | refreshing: false,
39 | episodes: this.props.route.params.episodes
40 | };
41 |
42 | this.refresh = this.refresh.bind(this);
43 | }
44 |
45 | async updateEpisodes() {
46 | try {
47 | const [
48 | updatedEpisodes
49 | ] = await Promise.all([
50 | axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}${this.state.season.key}?X-Plex-Token=${this.state.server.token}`)
51 | ]);
52 |
53 | this.setState({ episodes: updatedEpisodes.data.MediaContainer.Metadata });
54 | } catch (e) {
55 | Alert.alert('Error', 'Something went wrong during episodes fetch!');
56 | }
57 | };
58 |
59 | refresh() {
60 | this.setState({ refreshing: true });
61 |
62 | this.updateEpisodes()
63 | .finally(() => {
64 | this.setState({ refreshing: false });
65 | });
66 | }
67 |
68 | render() {
69 | return (
70 |
75 |
83 |
84 |
87 | }
88 | style={{
89 | backgroundColor: Colors.PlexGrey
90 | }}
91 | >
92 |
99 |
110 |
111 |
119 | {this.state.media.title} - {this.state.season.title}
120 |
121 |
122 |
123 |
124 | Summary:
125 | {this.state.season.summary}
126 |
127 |
128 |
129 |
136 |
139 | Episodes
140 |
141 | }
142 | isExpanded={this.state.episodesList}
143 | onPress={() => {
144 | this.setState({ episodesList: !this.state.episodesList });
145 | }}
146 | containerStyle={{
147 | backgroundColor: Colors.PlexBlack
148 | }}
149 | icon={}
150 | expandIcon={}
151 | >
152 | {this.state.episodes.map((episode, index) => {
153 | return (
154 | {
157 | this.props.navigation.navigate('SingleEpisode', {
158 | title: `${this.state.media.title} - ${this.state.season.title} - ${episode.title}`,
159 | server: this.state.server,
160 | episode: episode
161 | });
162 | }}
163 | containerStyle={{
164 | backgroundColor: Colors.PlexBlack
165 | }}
166 | >
167 | (
169 |
181 | )}
182 | overlayContainerStyle={{
183 | justifyContent: 'center',
184 | alignItems: 'center',
185 | }}
186 | />
187 |
188 |
189 |
194 | {episode.title}
195 |
196 |
197 |
202 | Duration: {episode.duration ? new Date(episode.duration).toISOString().slice(11, 19) : 'unknown'}
203 |
204 |
205 |
206 |
207 |
208 | );
209 | })}
210 |
211 |
212 |
213 |
214 | );
215 | }
216 | }
217 |
218 | export default (props) => {
219 | const navigation = useNavigation();
220 | return ;
221 | };
222 |
--------------------------------------------------------------------------------
/src/screens/SingleSession.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StyleSheet, View } from 'react-native';
3 | import { useNavigation } from '@react-navigation/native';
4 |
5 | class SingleSession extends React.Component {
6 | reason = 'RTFM';
7 |
8 | localStyle = StyleSheet.create({
9 | mainContainer: {
10 | flex: 1
11 | }
12 | })
13 |
14 | constructor(props) {
15 | super(props);
16 |
17 | this.state = {
18 | server: this.props.route.params.server,
19 | session: this.props.route.params.session
20 | };
21 | }
22 |
23 | render() {
24 | return (
25 |
28 |
29 | );
30 | }
31 | }
32 |
33 | export default (props) => {
34 | const navigation = useNavigation();
35 | return ;
36 | };
37 |
--------------------------------------------------------------------------------
/src/screens/TranscodingSessions.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import axios from 'axios';
3 | import { ScrollView, RefreshControl, Alert, StyleSheet } from 'react-native';
4 | import { Card, ListItem, Avatar } from '@rneui/themed';
5 | import { sessionTitle } from '../functions/ServerManageUtiles';
6 | import { useNavigation } from '@react-navigation/native';
7 |
8 | class TranscodingSessions extends React.Component {
9 | localStyle = StyleSheet.create({
10 | manageContainer: {
11 | flex: 1
12 | },
13 | serverIdLabel: {
14 | fontWeight: 'bold'
15 | },
16 | serverIdValue: {
17 | color: '#000000'
18 | },
19 | item: {
20 | color: '#000',
21 | width: '33%'
22 | },
23 | accordionTitle: {
24 | fontWeight: 'bold',
25 | fontSize: 14
26 | },
27 | updateAllView: {
28 | width: '100%'
29 | }
30 | });
31 |
32 | constructor(props) {
33 | super(props);
34 |
35 | this.state = {
36 | sessions: this.props.route.params.sessions,
37 | transcodingSessionsList: true,
38 | refreshing: false,
39 | server: this.props.route.params.server
40 | };
41 |
42 | this.refresh = this.refresh.bind(this);
43 | }
44 |
45 | async fetchActiveSessions() {
46 | try {
47 | let [
48 | activeSessions,
49 | transcodingSessions
50 | ] = await Promise.all([
51 | axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/status/sessions?X-Plex-Token=${this.state.server.token}`),
52 | axios.get(`${this.state.server.protocol}://${this.state.server.ip}:${this.state.server.port}/transcode/sessions?X-Plex-Token=${this.state.server.token}`)
53 | ]);
54 |
55 | let filteredTrancodeSession = [];
56 |
57 | transcodingSessions.data.MediaContainer.TranscodeSession.forEach(transcodingSession => {
58 | if (transcodingSession.videoDecision === 'transcode') {
59 | const filteredSessions = activeSessions.data.MediaContainer.Metadata.filter((activeSession) => {
60 | return activeSession.TranscodeSession.key === `/transcode/sessions/${transcodingSession.key}`;
61 | });
62 |
63 | filteredTrancodeSession.push(filteredSessions[0]);
64 | }
65 | });
66 |
67 | this.setState({ sessions: filteredTrancodeSession });
68 | } catch (e) {
69 | Alert.alert('Error', 'Something went wrong during transcoding sessions fetch!');
70 | }
71 | }
72 |
73 | refresh() {
74 | this.setState({ refreshing: true });
75 |
76 | this.fetchActiveSessions()
77 | .finally(() => {
78 | this.setState({ refreshing: false });
79 | });
80 | }
81 |
82 | render() {
83 | return (
84 |
87 | }
88 | >
89 |
90 | <>
91 |
94 | Transcoding Sessions
95 |
96 | }
97 | isExpanded={this.state.transcodingSessionsList}
98 | onPress={() => {
99 | this.setState({ transcodingSessionsList: !this.state.transcodingSessionsList });
100 | }}
101 | >
102 | {!this.state.sessions || this.state.sessions.length === 0 ? (
103 |
104 |
105 | No transcoding sessions
106 |
107 |
108 | ) : (
109 | this.state.sessions.map((session, index) => {
110 | return (
111 | {
115 | this.props.navigation.navigate('SingleSession', {
116 | title: session.Session.id,
117 | server: this.state.server,
118 | session: session
119 | });
120 | }}
121 | >
122 |
126 |
127 |
128 | {sessionTitle(session)}
129 | {session.Player.state}
130 | {session.Player.address}
131 | {session.Player.product}
132 | {session.Player.version}
133 | {session.Media[0].width} * {session.Media[0].height}
134 |
135 |
136 |
137 |
138 | );
139 | })
140 | )}
141 |
142 | >
143 |
144 |
145 | );
146 | }
147 | }
148 |
149 | export default (props) => {
150 | const navigation = useNavigation();
151 | return ;
152 | };
153 |
--------------------------------------------------------------------------------
/src/utiles/Colors.js:
--------------------------------------------------------------------------------
1 | export default class Colors {
2 | // Plex Branding Colors
3 | static PlexYellow = '#e5a00d';
4 | static PlexGrey = '#282a2d';
5 | static PlexBlack = '#1f1f1f';
6 |
7 | // Classic Colors
8 | static White = '#ffffff';
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react",
4 | "allowSyntheticDefaultImports": true,
5 | "esModuleInterop": true,
6 | },
7 | "extends": "@react-native/typescript-config/tsconfig.json"
8 | }
9 |
--------------------------------------------------------------------------------