@ -1,6 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
.env
|
|
||||||
Dockerfile
|
|
||||||
docker-compose*
|
|
45
.eslintrc.js
@ -1,45 +0,0 @@
|
|||||||
/**
|
|
||||||
* @type {import("eslint").Linter.Config}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
extends: [
|
|
||||||
"plugin:react/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
],
|
|
||||||
plugins: ["react", "@typescript-eslint"],
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es6: true,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "16.14.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
globals: {
|
|
||||||
Atomics: "readonly",
|
|
||||||
SharedArrayBuffer: "readonly",
|
|
||||||
},
|
|
||||||
ignorePatterns: [".eslintrc.js", "./src/reportWebVitals.ts"],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaFeatures: {
|
|
||||||
jsx: true,
|
|
||||||
},
|
|
||||||
ecmaVersion: 2018,
|
|
||||||
sourceType: "module",
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"prettier/prettier": "warn",
|
|
||||||
"react/prop-types": "off",
|
|
||||||
"linebreak-style": "off",
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
64
.gitignore
vendored
@ -1,22 +1,46 @@
|
|||||||
node_modules
|
# Miscellaneous
|
||||||
dist/
|
*.class
|
||||||
*.log
|
*.log
|
||||||
# user specific
|
*.pyc
|
||||||
cache/
|
*.swp
|
||||||
local/
|
.DS_Store
|
||||||
# debian build specific
|
.atom/
|
||||||
deb-struct/usr/lib/
|
.buildlog/
|
||||||
deb-struct/usr/bin/
|
.history
|
||||||
deb-struct/usr/share/applications
|
.svn/
|
||||||
# deply build binaries
|
|
||||||
deploy/linux/build
|
|
||||||
deploy/win32/build
|
|
||||||
deploy/darwin/build
|
|
||||||
# aur build specific
|
|
||||||
aur-struct/src
|
|
||||||
aur-struct/pkg
|
|
||||||
aur-struct/*.zip*
|
|
||||||
aur-struct/*.zst
|
|
||||||
|
|
||||||
*.tsbuildinfo
|
# IntelliJ related
|
||||||
tsconfig.tsbuildinfo
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
10
.metadata
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* @type {import("prettier").Options}
|
|
||||||
*/
|
|
||||||
const config = {
|
|
||||||
tabWidth: 4,
|
|
||||||
printWidth: 90,
|
|
||||||
semi: true,
|
|
||||||
endOfLine: "auto",
|
|
||||||
trailingComma: "all",
|
|
||||||
};
|
|
||||||
module.exports = config;
|
|
13
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Flutter",
|
||||||
|
"type": "dart",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "lib/main.dart"
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
"compounds": []
|
||||||
|
}
|
4
.vscode/settings.json
vendored
@ -1,3 +1 @@
|
|||||||
{
|
{}
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
|
||||||
}
|
|
10
.vscode/tasks.json
vendored
@ -1,12 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": []
|
||||||
{
|
|
||||||
"type": "npm",
|
|
||||||
"script": "start",
|
|
||||||
"problemMatcher": [],
|
|
||||||
"label": "npm: start",
|
|
||||||
"detail": "qode ./dist/index.js"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
108
README.md
@ -1,6 +1,6 @@
|
|||||||

|

|
||||||
|
|
||||||
Spotube is a [qt](https://qt.io) based lightweight spotify client which uses [nodegui/react-nodegui](https://github.com/nodegui/react-nodegui) as frontend & nodejs as backend. It utilizes the power of Spotify & Youtube's public API & creates a hazardless, performant & resource friendly User Experience
|
Spotube is a [Flutter](https://flutter.dev) based lightweight spotify client. It utilizes the power of Spotify & Youtube's public API & creates a hazardless, performant & resource friendly User Experience
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -10,36 +10,23 @@ Following are the features that currently spotube offers:
|
|||||||
- Open Source
|
- Open Source
|
||||||
- No telementry, diagnostics or user data collection
|
- No telementry, diagnostics or user data collection
|
||||||
- Lightweight & resource friendly
|
- Lightweight & resource friendly
|
||||||
- Near native performance & seemless with default desktop themes (Win10, Win7, OSX, QT-default)
|
- Near native performance (Thanks to Flutter+Skia)
|
||||||
- Playback control is on user's machine instead of server based
|
- Playback control is on user's machine instead of server based
|
||||||
- Small size & less data hungry
|
- Small size & less data hungry
|
||||||
- No spotify or youtube ads since it uses all public & free APIs (But it's recommended to support the creators by watching/liking/subscribing to the artists youtube channel or add as favourite track in spotify. Mostly buying spotify premium is the best way to support their valuable creations)
|
- No spotify or youtube ads since it uses all public & free APIs (But it's recommended to support the creators by watching/liking/subscribing to the artists youtube channel or add as favourite track in spotify. Mostly buying spotify premium is the best way to support their valuable creations)
|
||||||
- Lyrics
|
- Lyrics
|
||||||
- Downloadable track
|
- Downloadable track (WIP)
|
||||||
|
|
||||||
## Requirements (Linux🐧 only)
|
## Requirements (Linux🐧 only)
|
||||||
|
|
||||||
Don't worry **spotify premium isn't required**😱. But some extra packages are required.
|
Don't worry **spotify premium isn't required**😱. But some extra packages are required.
|
||||||
|
|
||||||
- [MPV](https://mpv.io/installation/) player for playing the actual audio
|
- [MPV](https://mpv.io/installation/) player for playing the actual audio
|
||||||
- [youtube-dl](https://github.com/ytdl-org/youtube-dl) for streaming the audio from youtube. It already comes pre bundled with mpv
|
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) for streaming the audio from youtube
|
||||||
|
|
||||||
> **Important for [Ubuntu/Debian]():** If you're using any **ubuntu/debian** based linux distro then **youtube-dl** installed from the typical **apt-get** repositories will most likely not work as that version is older than current release. So remove it & install from the repository manually
|
|
||||||
|
|
||||||
Remove the **youtube-dl** installed with **mpv** player or from **apt package manger**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ sudo apt-get remove youtube-dl
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, Install youtube-dl from
|
Now, Install youtube-dl from
|
||||||
|
|
||||||
- official github repo: https://github.com/ytdl-org/youtube-dl#installation (recommended)
|
- official github repo: https://github.com/yt-dlp/yt-dlp#installation (recommended)
|
||||||
**or**
|
|
||||||
- snap installation
|
|
||||||
```bash
|
|
||||||
$ snap install youtube-dl
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -69,7 +56,7 @@ Extract the **`Spotube-winx64-v<version>.zip`** & double click on **`install.bat
|
|||||||
|
|
||||||
There are some configurations that needs to be done to start using this software
|
There are some configurations that needs to be done to start using this software
|
||||||
|
|
||||||
You need a spotify account & a web app for
|
You need a spotify account & a developer app for
|
||||||
|
|
||||||
- clientId
|
- clientId
|
||||||
- clientSecret
|
- clientSecret
|
||||||
@ -88,94 +75,25 @@ You need a spotify account & a web app for
|
|||||||
- Click on **SHOW CLIENT SECRET** to reveal the **clientSecret**. Then copy the **clientID**, **clientSecret** & paste in the **Spotube's** respective fields
|
- Click on **SHOW CLIENT SECRET** to reveal the **clientSecret**. Then copy the **clientID**, **clientSecret** & paste in the **Spotube's** respective fields
|
||||||

|

|
||||||
|
|
||||||
|
Also, you need a [genius](https://genius.com) account for **lyrics** & a API Client for
|
||||||
|
|
||||||
|
- accessToken
|
||||||
|
|
||||||
> **Note!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself
|
> **Note!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself
|
||||||
|
|
||||||
### Building from source
|
|
||||||
|
|
||||||
**nodegui/react-nodegui** requires following packages to run
|
|
||||||
|
|
||||||
- [CMake](https://cmake.org/install/) 3.1 & up
|
|
||||||
- GCC v7
|
|
||||||
- Nodejs 12.x & up
|
|
||||||
|
|
||||||
**Windows Specific:**
|
|
||||||
|
|
||||||
- Visual Studio 2019
|
|
||||||
|
|
||||||
**MacOS & Linux specific:**
|
|
||||||
|
|
||||||
- Make
|
|
||||||
|
|
||||||
**Ubuntu/Debian based linux specific:**
|
|
||||||
Having `pkg-config build-essential mesa-common-dev libglu1-mesa-dev` is important
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ sudo apt-get install pkg-config build-essential mesa-common-dev libglu1-mesa-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
After having this dependencies set up run following commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git clone https://github.com/KRTirtho/spotube.git
|
|
||||||
$ cd spotube
|
|
||||||
$ npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
Now start building:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm run build
|
|
||||||
$ npm run pack
|
|
||||||
```
|
|
||||||
|
|
||||||
Go to built package directory replace `os-name` with `linux`|`win32`|`darwin`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ cd deploy/<os-name>/build/spotube
|
|
||||||
```
|
|
||||||
|
|
||||||
If everything went smoothly then double clicking on the
|
|
||||||
|
|
||||||
- `AppRun` or Spotube-x86_64.AppImage for **linux**
|
|
||||||
- qode.exe for **Windows**
|
|
||||||
- Spotube-x86_64.dmg for **MacOS**
|
|
||||||
|
|
||||||
should work just fine without any problem
|
|
||||||
|
|
||||||
### Development
|
|
||||||
|
|
||||||
Follow the **Build from Source** guideline till `npm install`
|
|
||||||
Now, to start the dev server run the command in one terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
To start the application in development environment run following command in another terminal keeping the dev server running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
There will be some glitches, lags & stuck motions because of the library Spotube is currently using under the hood. It has some issues with layouts thus sometimes some contents aren't shown or overflows out of the window. But resizing the window would fix this issue. Soon there will be some updates fixing this sort of layout related problems
|
|
||||||
|
|
||||||
## TODO:
|
## TODO:
|
||||||
|
|
||||||
- [ ] Compile, Debug & Build for **MacOS**
|
- [ ] Compile, Debug & Build for **MacOS**
|
||||||
- [x] Add support for show Lyric of currently playing track
|
- [x] Add support for show Lyric of currently playing track
|
||||||
- [x] Track download
|
- [ ] Track download
|
||||||
- [x] Cached playback
|
|
||||||
- [ ] Support for playing/streaming podcasts/shows
|
- [ ] Support for playing/streaming podcasts/shows
|
||||||
- [x] Easy installation procedure/mechanism for simplicity in Windows
|
|
||||||
- [ ] Artist, User & Album pages
|
- [ ] Artist, User & Album pages
|
||||||
|
|
||||||
## Things that don't work
|
## Things that don't work
|
||||||
|
|
||||||
- Shows & Podcasts aren't supported as it'd require premium anyway
|
- Shows & Podcasts aren't supported as it'd require premium anyway
|
||||||
- Beautiful UI (you missed it, see the title😂👆)
|
|
||||||
- OS Media Controls
|
- OS Media Controls
|
||||||
|
- Global Media Shortcuts/Keyboard Media Buttons
|
||||||
|
|
||||||
#### Social handlers
|
#### Social handlers
|
||||||
|
|
||||||
|
30
analysis_options.yaml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at
|
||||||
|
# https://dart-lang.github.io/linter/lints/index.html.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
file_names: false
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
13
android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
68
android/app/build.gradle
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "com.example.spotube"
|
||||||
|
minSdkVersion flutter.minSdkVersion
|
||||||
|
targetSdkVersion flutter.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
}
|
7
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.spotube">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
34
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.spotube">
|
||||||
|
<application
|
||||||
|
android:label="spotube"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.example.spotube
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity() {
|
||||||
|
}
|
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
18
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
7
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.spotube">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
31
android/build.gradle
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.50'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
3
android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Fri Jun 23 08:50:38 CEST 2017
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
11
android/settings.gradle
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
6
assets.d.ts
vendored
@ -1,6 +0,0 @@
|
|||||||
declare module "*.svg";
|
|
||||||
declare module "*.png";
|
|
||||||
declare module "*.jpg";
|
|
||||||
declare module "*.jpeg";
|
|
||||||
declare module "*.gif";
|
|
||||||
declare module "*.bmp";
|
|
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="angle-left" class="svg-inline--fa fa-angle-left fa-w-8" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 512"><path fill="currentColor" d="M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z"></path></svg>
|
|
Before Width: | Height: | Size: 427 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="backward" class="svg-inline--fa fa-backward fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M11.5 280.6l192 160c20.6 17.2 52.5 2.8 52.5-24.6V96c0-27.4-31.9-41.8-52.5-24.6l-192 160c-15.3 12.8-15.3 36.4 0 49.2zm256 0l192 160c20.6 17.2 52.5 2.8 52.5-24.6V96c0-27.4-31.9-41.8-52.5-24.6l-192 160c-15.3 12.8-15.3 36.4 0 49.2z"></path></svg>
|
|
Before Width: | Height: | Size: 463 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="download" class="svg-inline--fa fa-download fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M216 0h80c13.3 0 24 10.7 24 24v168h87.7c17.8 0 26.7 21.5 14.1 34.1L269.7 378.3c-7.5 7.5-19.8 7.5-27.3 0L90.1 226.1c-12.6-12.6-3.7-34.1 14.1-34.1H192V24c0-13.3 10.7-24 24-24zm296 376v112c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V376c0-13.3 10.7-24 24-24h146.7l49 49c20.1 20.1 52.5 20.1 72.6 0l49-49H488c13.3 0 24 10.7 24 24zm-124 88c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20zm64 0c0-11-9-20-20-20s-20 9-20 20 9 20 20 20 20-9 20-20z"></path></svg>
|
|
Before Width: | Height: | Size: 678 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="forward" class="svg-inline--fa fa-forward fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M500.5 231.4l-192-160C287.9 54.3 256 68.6 256 96v320c0 27.4 31.9 41.8 52.5 24.6l192-160c15.3-12.8 15.3-36.4 0-49.2zm-256 0l-192-160C31.9 54.3 0 68.6 0 96v320c0 27.4 31.9 41.8 52.5 24.6l192-160c15.3-12.8 15.3-36.4 0-49.2z"></path></svg>
|
|
Before Width: | Height: | Size: 454 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="heart" class="svg-inline--fa fa-heart fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M458.4 64.3C400.6 15.7 311.3 23 256 79.3 200.7 23 111.4 15.6 53.6 64.3-21.6 127.6-10.6 230.8 43 285.5l175.4 178.7c10 10.2 23.4 15.9 37.6 15.9 14.3 0 27.6-5.6 37.6-15.8L469 285.6c53.5-54.7 64.7-157.9-10.6-221.3zm-23.6 187.5L259.4 430.5c-2.4 2.4-4.4 2.4-6.8 0L77.2 251.8c-36.5-37.2-43.9-107.6 7.3-150.7 38.9-32.7 98.9-27.8 136.5 10.5l35 35.7 35-35.7c37.8-38.5 97.8-43.2 136.5-10.6 51.1 43.1 43.5 113.9 7.3 150.8z"></path></svg>
|
|
Before Width: | Height: | Size: 640 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="heart" class="svg-inline--fa fa-heart fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z"></path></svg>
|
|
Before Width: | Height: | Size: 437 B |
217
assets/icon.svg
@ -1,217 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
version="1.1"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
viewBox="0 0 1024 1024"
|
|
||||||
width="1024"
|
|
||||||
height="1024"
|
|
||||||
id="svg164"
|
|
||||||
sodipodi:docname="Spotube Icon.svg"
|
|
||||||
inkscape:export-filename="/home/krtirtho/development/desktop-dev/spotube/deploy/linux/spotube/Spotube Icon.png"
|
|
||||||
inkscape:export-xdpi="96"
|
|
||||||
inkscape:export-ydpi="96"
|
|
||||||
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
|
|
||||||
<metadata
|
|
||||||
id="metadata168">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<sodipodi:namedview
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1"
|
|
||||||
objecttolerance="10"
|
|
||||||
gridtolerance="10"
|
|
||||||
guidetolerance="10"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:window-width="1280"
|
|
||||||
inkscape:window-height="957"
|
|
||||||
id="namedview166"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="0.71289062"
|
|
||||||
inkscape:cx="223.73699"
|
|
||||||
inkscape:cy="427.38956"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg164" />
|
|
||||||
<defs
|
|
||||||
id="defs116">
|
|
||||||
<path
|
|
||||||
d="M864.5 488.49C864.5 680.95 708.5 837.21 516.36 837.21C324.22 837.21 168.22 680.95 168.22 488.49C168.22 296.03 324.22 139.78 516.36 139.78C708.5 139.78 864.5 296.03 864.5 488.49Z"
|
|
||||||
id="amHaSVpu" />
|
|
||||||
<path
|
|
||||||
d=""
|
|
||||||
id="a2BJMy7Jb" />
|
|
||||||
<path
|
|
||||||
d="M311.12 310.07C313.34 313.32 304.24 320.57 310.85 329.83C318.01 339.85 343.74 336.24 360.41 316.59C372.91 301.85 475.1 266.53 511.85 269.93C588.19 276.99 575.39 271.83 590.61 281.1C596.7 284.81 639.52 293.78 662.13 310.85C684.73 327.91 711.65 343.39 715.92 338.17C725.54 326.39 743.34 305.37 658.01 254.46C616.69 229.81 620.32 239.57 587.05 230.59C533.9 216.24 472.16 220.95 401.83 244.73C336.72 281.52 306.48 303.3 311.12 310.07Z"
|
|
||||||
id="a1tDWZImgU" />
|
|
||||||
<path
|
|
||||||
d="M411.75 413.54C413 415.03 367.9 428.18 360.73 442.12C349.52 463.92 375.04 487.76 385.62 472.66C392.39 463 485.79 428.29 508.37 428.23C555.27 428.11 596.35 438.36 606.16 443.1C614 446.9 672.19 473.83 676.79 467C681.97 459.31 702.29 441.03 647.4 415.1C607.69 396.35 600.7 394.71 570 390.75C547.31 387.83 524.26 387.05 500.84 388.41C438.23 401.33 408.53 409.7 411.75 413.54Z"
|
|
||||||
id="d5X4Y3iXxi" />
|
|
||||||
<path
|
|
||||||
d="M401.51 326.48C402.99 328.06 352.9 339.29 345.48 353.13C333.86 374.78 363.61 400.24 374.82 385.38C381.99 375.88 485.36 345.01 510.73 346.03C563.41 348.13 610.02 360.52 621.25 365.81C630.24 370.04 696.82 400.2 701.68 393.48C707.16 385.9 729.17 368.27 666.33 339.28C620.88 318.31 612.95 316.31 578.28 310.83C552.66 306.77 526.72 304.88 500.48 305.15C430.72 315.32 397.73 322.42 401.51 326.48Z"
|
|
||||||
id="caQrsW984" />
|
|
||||||
<path
|
|
||||||
d="M538.91 551.22C529.19 537.59 505.67 536.18 497.12 555.44C488.61 574.6 420.53 727.92 412.02 747.09C404.35 764.36 418.47 783.09 436.92 780.92C457.39 778.53 621.12 759.48 641.58 757.1C659.83 755 669.2 734.02 658.48 719.06C634.56 685.49 550.87 568 538.91 551.22Z"
|
|
||||||
id="gcFKZeS95" />
|
|
||||||
<path
|
|
||||||
d="M520.79 470.72C521.19 474.45 518.48 477.8 514.75 478.2C512.05 478.49 510.72 478.63 508.02 478.92C504.28 479.32 500.93 476.61 500.53 472.88C500.01 467.97 499.16 460.01 498.64 455.1C498.24 451.36 500.94 448.01 504.68 447.62C507.38 447.33 508.71 447.19 511.41 446.9C515.14 446.5 518.49 449.2 518.89 452.94C519.42 457.85 520.27 465.81 520.79 470.72Z"
|
|
||||||
id="aFNm6T4Ou" />
|
|
||||||
<path
|
|
||||||
d="M525.36 513.59C525.76 517.32 523.06 520.67 519.32 521.07C516.62 521.36 515.29 521.5 512.59 521.79C508.86 522.19 505.51 519.48 505.11 515.75C504.58 510.84 503.73 502.88 503.21 497.97C502.81 494.23 505.52 490.88 509.25 490.48C511.95 490.2 513.28 490.05 515.98 489.77C519.72 489.37 523.07 492.07 523.47 495.81C523.99 500.71 524.84 508.68 525.36 513.59Z"
|
|
||||||
id="f44lxYrdu7" />
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
id="g162"
|
|
||||||
transform="matrix(1.3645108,0,0,1.3645108,-191.6942,-155.16566)">
|
|
||||||
<g
|
|
||||||
id="g160">
|
|
||||||
<g
|
|
||||||
id="g120">
|
|
||||||
<use
|
|
||||||
xlink:href="#amHaSVpu"
|
|
||||||
opacity="1"
|
|
||||||
fill="#1db954"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use118"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g126">
|
|
||||||
<g
|
|
||||||
id="g124">
|
|
||||||
<use
|
|
||||||
xlink:href="#a2BJMy7Jb"
|
|
||||||
opacity="1"
|
|
||||||
fill-opacity="0"
|
|
||||||
stroke="#000000"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-opacity="1"
|
|
||||||
id="use122"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g140">
|
|
||||||
<g
|
|
||||||
id="g130">
|
|
||||||
<use
|
|
||||||
xlink:href="#a1tDWZImgU"
|
|
||||||
opacity="1"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use128"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g134">
|
|
||||||
<use
|
|
||||||
xlink:href="#d5X4Y3iXxi"
|
|
||||||
opacity="1"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use132"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g138">
|
|
||||||
<use
|
|
||||||
xlink:href="#caQrsW984"
|
|
||||||
opacity="1"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use136"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g148">
|
|
||||||
<use
|
|
||||||
xlink:href="#gcFKZeS95"
|
|
||||||
opacity="1"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use142"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
<g
|
|
||||||
id="g146">
|
|
||||||
<use
|
|
||||||
xlink:href="#gcFKZeS95"
|
|
||||||
opacity="1"
|
|
||||||
fill-opacity="0"
|
|
||||||
stroke="#000000"
|
|
||||||
stroke-width="1"
|
|
||||||
stroke-opacity="0"
|
|
||||||
id="use144"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g158">
|
|
||||||
<g
|
|
||||||
id="g152">
|
|
||||||
<use
|
|
||||||
xlink:href="#aFNm6T4Ou"
|
|
||||||
opacity="1"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use150"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
id="g156">
|
|
||||||
<use
|
|
||||||
xlink:href="#f44lxYrdu7"
|
|
||||||
opacity="1"
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="use154"
|
|
||||||
x="0"
|
|
||||||
y="0"
|
|
||||||
width="100%"
|
|
||||||
height="100%" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 19 KiB |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="music" class="svg-inline--fa fa-music fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M470.38 1.51L150.41 96A32 32 0 0 0 128 126.51v261.41A139 139 0 0 0 96 384c-53 0-96 28.66-96 64s43 64 96 64 96-28.66 96-64V214.32l256-75v184.61a138.4 138.4 0 0 0-32-3.93c-53 0-96 28.66-96 64s43 64 96 64 96-28.65 96-64V32a32 32 0 0 0-41.62-30.49z"></path></svg>
|
|
Before Width: | Height: | Size: 474 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="pause" class="svg-inline--fa fa-pause fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M144 479H48c-26.5 0-48-21.5-48-48V79c0-26.5 21.5-48 48-48h96c26.5 0 48 21.5 48 48v352c0 26.5-21.5 48-48 48zm304-48V79c0-26.5-21.5-48-48-48h-96c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h96c26.5 0 48-21.5 48-48z"></path></svg>
|
|
Before Width: | Height: | Size: 444 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="play" class="svg-inline--fa fa-play fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"></path></svg>
|
|
Before Width: | Height: | Size: 339 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="random" class="svg-inline--fa fa-random fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504.971 359.029c9.373 9.373 9.373 24.569 0 33.941l-80 79.984c-15.01 15.01-40.971 4.49-40.971-16.971V416h-58.785a12.004 12.004 0 0 1-8.773-3.812l-70.556-75.596 53.333-57.143L352 336h32v-39.981c0-21.438 25.943-31.998 40.971-16.971l80 79.981zM12 176h84l52.781 56.551 53.333-57.143-70.556-75.596A11.999 11.999 0 0 0 122.785 96H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12zm372 0v39.984c0 21.46 25.961 31.98 40.971 16.971l80-79.984c9.373-9.373 9.373-24.569 0-33.941l-80-79.981C409.943 24.021 384 34.582 384 56.019V96h-58.785a12.004 12.004 0 0 0-8.773 3.812L96 336H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h110.785c3.326 0 6.503-1.381 8.773-3.812L352 176h32z"></path></svg>
|
|
Before Width: | Height: | Size: 904 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="search" class="svg-inline--fa fa-search fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z"></path></svg>
|
|
Before Width: | Height: | Size: 577 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="cog" class="svg-inline--fa fa-cog fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"></path></svg>
|
|
Before Width: | Height: | Size: 1.0 KiB |
BIN
assets/spotube-logo.png
Normal file
After Width: | Height: | Size: 76 KiB |
71
assets/spotube-logo.svg
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:bx="https://boxy-svg.com">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="gradient-2-0" gradientUnits="userSpaceOnUse" cx="251.179" cy="248.821" r="241.45" gradientTransform="matrix(1, 0, 0, 1, -1.768285, 0.589104)" xlink:href="#gradient-2"/>
|
||||||
|
<linearGradient id="gradient-2">
|
||||||
|
<stop offset="0.841" style="stop-color: rgb(255, 255, 255);"/>
|
||||||
|
<stop offset="1" style="stop-color: rgb(201, 201, 201);"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="drop-shadow-filter-0" x="-500%" y="-500%" width="1000%" height="1000%" bx:preset="drop-shadow 1 0 0 10 0.42 rgba(201,201,201,1)">
|
||||||
|
<feGaussianBlur in="SourceAlpha" stdDeviation="10"/>
|
||||||
|
<feOffset dx="0" dy="0"/>
|
||||||
|
<feComponentTransfer result="offsetblur">
|
||||||
|
<feFuncA id="spread-ctrl" type="linear" slope="0.84"/>
|
||||||
|
</feComponentTransfer>
|
||||||
|
<feFlood flood-color="rgba(201,201,201,1)"/>
|
||||||
|
<feComposite in2="offsetblur" operator="in"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="gradient-4-3" gradientUnits="userSpaceOnUse" x1="47.146" y1="18.044" x2="47.146" y2="75.354" xlink:href="#gradient-4"/>
|
||||||
|
<linearGradient id="gradient-4">
|
||||||
|
<stop offset="0.113" style="stop-color: rgb(83, 240, 111);"/>
|
||||||
|
<stop offset="0.608" style="stop-color: rgb(0, 177, 86);"/>
|
||||||
|
<stop offset="0.944" style="stop-color: rgb(2, 167, 156);"/>
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="inner-shadow-filter-0" x="-500%" y="-500%" width="1000%" height="1000%" bx:preset="inner-shadow 1 0 0 4 0.5 rgba(0,0,0,0.7)">
|
||||||
|
<feOffset dx="0" dy="0"/>
|
||||||
|
<feGaussianBlur stdDeviation="4"/>
|
||||||
|
<feComposite operator="out" in="SourceGraphic"/>
|
||||||
|
<feComponentTransfer result="choke">
|
||||||
|
<feFuncA type="linear" slope="1"/>
|
||||||
|
</feComponentTransfer>
|
||||||
|
<feFlood flood-color="rgba(0,0,0,0.7)" result="color"/>
|
||||||
|
<feComposite operator="in" in="color" in2="choke" result="shadow"/>
|
||||||
|
<feComposite operator="over" in="shadow" in2="SourceGraphic"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="gradient-4-1" gradientUnits="userSpaceOnUse" x1="82.026" y1="144.832" x2="82.026" y2="264.462" xlink:href="#gradient-4"/>
|
||||||
|
<linearGradient id="gradient-4-2" gradientUnits="userSpaceOnUse" x1="143.693" y1="22.804" x2="143.693" y2="264.582" xlink:href="#gradient-4"/>
|
||||||
|
<linearGradient id="gradient-4-0" gradientUnits="userSpaceOnUse" x1="205.862" y1="146.28" x2="205.862" y2="265.91" xlink:href="#gradient-4"/>
|
||||||
|
</defs>
|
||||||
|
<ellipse style="paint-order: fill; filter: url(#drop-shadow-filter-0); fill: url(#gradient-2-0);" cx="249.41" cy="249.41" rx="241.45" ry="241.45"/>
|
||||||
|
<g transform="matrix(0.319972, 0, 0, 0.323174, 248.635162, 304.74234)" style="">
|
||||||
|
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(-175.05 -175.05000000000004) scale(3.89 3.89)">
|
||||||
|
<path d="M 91.835 18.32 C 91.637 18.132 91.374 18.036 91.096 18.046 C 84.617 18.367 77.578 19.444 68.948 21.435 C 68.677 21.498 68.444 21.67 68.305 21.911 C 68.166 22.152 68.135 22.44 68.217 22.705 L 69.055 25.409 C 62.692 22.996 53.742 21.607 45.995 21.912 C 43.155 21.912 39.913 23.321 36.95 25.412 L 25.235 25.412 L 26.074 22.704 C 26.157 22.438 26.124 22.151 25.986 21.91 C 25.848 21.669 25.615 21.496 25.344 21.434 C 16.714 19.443 9.676 18.366 3.196 18.045 C 2.927 18.033 2.656 18.13 2.457 18.319 C 2.258 18.509 2.146 18.771 2.146 19.045 L 2.146 53.387 C 2.146 53.94 2.594 54.387 3.146 54.387 L 15.524 54.387 C 15.962 54.387 16.35 54.102 16.479 53.683 L 16.951 52.16 C 27.138 64.032 37.497 74.935 45.585 74.935 C 47.142 74.935 48.614 74.524 49.986 73.645 L 50.197 73.859 C 51.141 74.815 52.406 75.346 53.758 75.354 C 53.769 75.354 53.779 75.354 53.79 75.354 C 55.129 75.354 56.387 74.839 57.336 73.903 C 58.17 73.078 58.635 72.03 58.772 70.947 C 59.702 71.718 60.833 72.127 61.978 72.127 C 63.259 72.126 64.542 71.643 65.525 70.673 C 66.634 69.577 67.105 68.092 66.981 66.648 C 67.427 66.758 67.878 66.833 68.331 66.833 C 69.614 66.833 70.869 66.39 71.779 65.491 C 72.735 64.547 73.266 63.282 73.274 61.93 C 73.282 60.578 72.767 59.308 71.887 58.423 C 71.519 57.97 71.139 57.535 70.766 57.089 L 77.582 52.94 L 77.812 53.682 C 77.942 54.101 78.329 54.386 78.767 54.386 L 91.146 54.386 C 91.699 54.386 92.146 53.939 92.146 53.386 L 92.146 19.045 C 92.146 18.771 92.034 18.509 91.835 18.32 Z M 14.787 52.387 L 4.146 52.387 L 4.146 20.102 C 9.952 20.461 16.268 21.437 23.845 23.145 L 14.787 52.387 Z M 70.373 64.067 C 69.234 65.193 67.063 65.072 65.817 63.809 C 65.8 63.792 65.778 63.786 65.76 63.771 C 65.693 63.694 65.642 63.608 65.569 63.534 L 54.619 52.448 C 54.229 52.056 53.598 52.052 53.204 52.439 C 52.811 52.828 52.808 53.46 53.195 53.854 L 64.145 64.94 C 64.714 65.515 65.025 66.283 65.02 67.1 C 65.015 67.916 64.695 68.68 64.119 69.248 C 62.924 70.431 60.991 70.416 59.809 69.222 L 57.384 66.767 C 57.382 66.765 57.381 66.763 57.38 66.762 L 46.43 55.677 C 46.041 55.286 45.408 55.281 45.016 55.668 C 44.623 56.057 44.619 56.689 45.007 57.083 L 55.956 68.169 C 57.138 69.364 57.126 71.298 55.93 72.479 C 54.734 73.661 52.8 73.647 51.62 72.453 L 38.24 58.908 C 37.851 58.516 37.218 58.51 36.826 58.9 C 36.433 59.288 36.429 59.921 36.817 60.314 L 48.528 72.169 C 41.093 76.143 28.778 62.93 17.651 49.901 L 24.616 27.414 L 34.431 27.414 C 31.69 29.846 29.43 32.75 28.339 35.397 C 26.943 38.783 27.852 40.687 28.86 41.688 C 28.886 41.714 28.914 41.739 28.943 41.762 C 32.786 44.809 36.571 45.577 42.466 39.479 C 44.467 39.601 46.171 39.254 47.65 38.415 C 55.956 44.222 63.587 51.376 70.399 59.758 C 71.581 60.953 71.569 62.887 70.373 64.067 Z M 69.464 55.541 C 63.058 48.131 55.937 41.698 48.248 36.395 C 47.907 36.159 47.455 36.159 47.114 36.394 C 45.792 37.301 44.175 37.645 42.17 37.449 C 41.859 37.415 41.556 37.533 41.343 37.759 C 35.758 43.702 32.999 42.415 30.234 40.232 C 29.238 39.193 29.657 37.448 30.188 36.159 C 32.412 30.761 40.301 23.913 46.034 23.912 C 54.206 23.599 63.683 25.188 69.82 27.879 L 76.972 50.97 L 69.464 55.541 Z M 90.146 52.387 L 79.504 52.387 L 70.446 23.145 C 78.023 21.437 84.34 20.461 90.145 20.102 L 90.145 52.387 Z" style="stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill-rule: nonzero; opacity: 1; fill: rgb(28, 28, 29); stroke-width: 3.33887px; paint-order: stroke; stroke: url(#gradient-4-3);" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1.107829, 0, 0, 1.106267, 91.508957, 66.745735)" style="filter: url(#inner-shadow-filter-0);">
|
||||||
|
<g>
|
||||||
|
<path d="M 71.421 155.437 L 71.421 253.857 C 71.421 259.724 76.162 264.462 82.026 264.462 C 87.88 264.462 92.631 259.724 92.631 253.857 L 92.631 155.437 C 92.631 149.581 87.88 144.832 82.026 144.832 C 76.162 144.832 71.421 149.576 71.421 155.437 Z" style="stroke-width: 9.80924px; stroke: url(#gradient-4-1); fill: rgb(29, 29, 29); stroke-linecap: round; stroke-linejoin: round;"/>
|
||||||
|
<path d="M29.456,264.582h23.351v-116.85c0.064-0.56,0.166-1.119,0.166-1.693c0-50.412,40.69-91.42,90.698-91.42 c50.002,0,90.692,41.008,90.692,91.42c0,0.771,0.113,1.518,0.228,2.263v116.28h23.354c16.254,0,29.442-13.64,29.442-30.469 v-60.936c0-13.878-8.989-25.57-21.261-29.249c-1.129-66.971-55.608-121.124-122.45-121.124 c-66.86,0-121.347,54.158-122.465,121.15C8.956,147.638,0,159.32,0,173.187v60.926C0,250.932,13.187,264.582,29.456,264.582z" style="stroke-width: 11.3184px; stroke: url(#gradient-4-2); fill: rgb(29, 29, 29); stroke-linecap: round; stroke-linejoin: round;"/>
|
||||||
|
<path d="M 195.258 156.885 L 195.258 255.305 C 195.258 261.172 200.006 265.91 205.862 265.91 C 211.718 265.91 216.466 261.172 216.466 255.305 L 216.466 156.885 C 216.466 151.029 211.718 146.28 205.862 146.28 C 199.995 146.28 195.258 151.024 195.258 156.885 Z" style="stroke-width: 9.80924px; stroke: url(#gradient-4-0); fill: rgb(29, 29, 29); stroke-linecap: round; stroke-linejoin: round;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
<g transform="matrix(0.972684, 0, 0, 0.972684, 62.617451, 9.850071)" style=""/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 380 KiB After Width: | Height: | Size: 732 KiB |
BIN
assets/spotube_banner.png
Normal file
After Width: | Height: | Size: 273 KiB |
Before Width: | Height: | Size: 736 KiB |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="stop" class="svg-inline--fa fa-stop fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z"></path></svg>
|
|
Before Width: | Height: | Size: 333 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="times" class="svg-inline--fa fa-times fa-w-11" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
|
Before Width: | Height: | Size: 645 B |
@ -1,42 +0,0 @@
|
|||||||
import { build } from "esbuild";
|
|
||||||
import { mkdir, copyFile } from "fs/promises";
|
|
||||||
import { join } from "path";
|
|
||||||
|
|
||||||
build({
|
|
||||||
bundle: true,
|
|
||||||
outdir: "./dist",
|
|
||||||
minify: false,
|
|
||||||
platform: 'node',
|
|
||||||
target: "es6",
|
|
||||||
color: true,
|
|
||||||
jsx: "transform",
|
|
||||||
sourcemap: true,
|
|
||||||
tsconfig: "./tsconfig.json",
|
|
||||||
entryPoints: ["src/index.tsx"],
|
|
||||||
target: ['node12'],
|
|
||||||
loader: {
|
|
||||||
".png": "file",
|
|
||||||
".jpg": "file",
|
|
||||||
".gif": "file",
|
|
||||||
".svg": "file",
|
|
||||||
".node": "file"
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
{
|
|
||||||
name: "Native Addon",
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /\.node$/ }, async (arg) => {
|
|
||||||
const pluginName = arg.path.split("/").reverse()[0]
|
|
||||||
mkdir(join(process.cwd(), "dist"), {recursive: true})
|
|
||||||
await copyFile(join(arg.resolveDir, arg.path), join(process.cwd(), "dist", pluginName))
|
|
||||||
return {
|
|
||||||
external: true,
|
|
||||||
path: "./"+pluginName,
|
|
||||||
namespace: "js",
|
|
||||||
pluginName
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}).catch(() => process.exit(1));
|
|
34
ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>9.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1
ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
1
ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "Generated.xcconfig"
|
481
ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,481 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 50;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1300;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.spotube;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.spotube;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.spotube;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
87
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1300"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
13
ios/Runner/AppDelegate.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 564 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
47
ios/Runner/Info.plist
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Sptube</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>spotube</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
87
lib/components/CategoryCard.dart
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/material.dart' hide Page;
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/PlaylistCard.dart';
|
||||||
|
import 'package:spotube/components/PlaylistGenreView.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
class CategoryCard extends StatefulWidget {
|
||||||
|
final Category category;
|
||||||
|
final Iterable<PlaylistSimple>? playlists;
|
||||||
|
const CategoryCard(
|
||||||
|
this.category, {
|
||||||
|
Key? key,
|
||||||
|
this.playlists,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CategoryCardState createState() => _CategoryCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CategoryCardState extends State<CategoryCard> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.category.name ?? "Unknown",
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return PlaylistGenreView(
|
||||||
|
widget.category.id!,
|
||||||
|
widget.category.name!,
|
||||||
|
playlists: widget.playlists,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text("See all"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Consumer<SpotifyDI>(
|
||||||
|
builder: (context, data, child) {
|
||||||
|
return FutureBuilder<Iterable<PlaylistSimple>>(
|
||||||
|
future: widget.playlists == null
|
||||||
|
? (widget.category.id != "user-featured-playlists"
|
||||||
|
? data.spotifyApi.playlists
|
||||||
|
.getByCategoryId(widget.category.id!)
|
||||||
|
: data.spotifyApi.playlists.featured)
|
||||||
|
.getPage(4, 0)
|
||||||
|
.then((value) => value.items ?? [])
|
||||||
|
: Future.value(widget.playlists),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return const Center(child: Text("Error occurred"));
|
||||||
|
}
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator.adaptive(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Wrap(
|
||||||
|
spacing: 20,
|
||||||
|
runSpacing: 20,
|
||||||
|
children: snapshot.data!
|
||||||
|
.map((playlist) => PlaylistCard(playlist))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
253
lib/components/Home.dart
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart' hide Page;
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:spotify/spotify.dart' hide Image;
|
||||||
|
import 'package:spotube/components/CategoryCard.dart';
|
||||||
|
import 'package:spotube/components/Login.dart';
|
||||||
|
import 'package:spotube/components/Lyrics.dart';
|
||||||
|
import 'package:spotube/components/PageWindowTitleBar.dart';
|
||||||
|
import 'package:spotube/components/Player.dart' as player;
|
||||||
|
import 'package:spotube/components/Settings.dart';
|
||||||
|
import 'package:spotube/components/UserLibrary.dart';
|
||||||
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
|
import 'package:spotube/models/sideBarTiles.dart';
|
||||||
|
import 'package:spotube/provider/Auth.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
List<String> spotifyScopes = [
|
||||||
|
"user-library-read",
|
||||||
|
"user-library-modify",
|
||||||
|
"user-read-private",
|
||||||
|
"user-read-email",
|
||||||
|
"playlist-read-collaborative"
|
||||||
|
];
|
||||||
|
|
||||||
|
class Home extends StatefulWidget {
|
||||||
|
const Home({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_HomeState createState() => _HomeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeState extends State<Home> {
|
||||||
|
final PagingController<int, Category> _pagingController =
|
||||||
|
PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
||||||
|
try {
|
||||||
|
Auth authProvider = context.read<Auth>();
|
||||||
|
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||||
|
var clientId = localStorage.getString(LocalStorageKeys.clientId);
|
||||||
|
var clientSecret =
|
||||||
|
localStorage.getString(LocalStorageKeys.clientSecret);
|
||||||
|
var accessToken = localStorage.getString(LocalStorageKeys.accessToken);
|
||||||
|
var refreshToken =
|
||||||
|
localStorage.getString(LocalStorageKeys.refreshToken);
|
||||||
|
var expirationStr = localStorage.getString(LocalStorageKeys.expiration);
|
||||||
|
var expiration =
|
||||||
|
expirationStr != null ? DateTime.parse(expirationStr) : null;
|
||||||
|
|
||||||
|
if (clientId != null && clientSecret != null) {
|
||||||
|
SpotifyApi spotifyApi = SpotifyApi(
|
||||||
|
SpotifyApiCredentials(
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
accessToken: accessToken,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
expiration: expiration,
|
||||||
|
scopes: spotifyScopes,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
SpotifyApiCredentials credentials = await spotifyApi.getCredentials();
|
||||||
|
if (credentials.accessToken?.isNotEmpty ?? false) {
|
||||||
|
authProvider.setAuthState(
|
||||||
|
clientId: clientId,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
accessToken:
|
||||||
|
credentials.accessToken, // accessToken can be new/refreshed
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
expiration: credentials.expiration,
|
||||||
|
isLoggedIn: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_pagingController.addPageRequestListener((pageKey) async {
|
||||||
|
try {
|
||||||
|
SpotifyDI data = context.read<SpotifyDI>();
|
||||||
|
Page<Category> categories = await data.spotifyApi.categories
|
||||||
|
.list(country: "US")
|
||||||
|
.getPage(15, pageKey);
|
||||||
|
|
||||||
|
var items = categories.items!.toList();
|
||||||
|
if (pageKey == 0) {
|
||||||
|
Category category = Category();
|
||||||
|
category.id = "user-featured-playlists";
|
||||||
|
category.name = "Featured";
|
||||||
|
items.insert(0, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categories.isLast && categories.items != null) {
|
||||||
|
_pagingController.appendLastPage(items);
|
||||||
|
} else if (categories.items != null) {
|
||||||
|
_pagingController.appendPage(items, categories.nextOffset);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_pagingController.error = e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print("[login state error]: $e");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pagingController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Auth authProvider = Provider.of<Auth>(context);
|
||||||
|
if (!authProvider.isLoggedIn) {
|
||||||
|
return Login();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
WindowTitleBarBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 256),
|
||||||
|
color:
|
||||||
|
Theme.of(context).navigationRailTheme.backgroundColor,
|
||||||
|
child: MoveWindow(),
|
||||||
|
),
|
||||||
|
Expanded(child: MoveWindow()),
|
||||||
|
const TitleBarActionButtons(),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
NavigationRail(
|
||||||
|
destinations: sidebarTileList
|
||||||
|
.map((e) => NavigationRailDestination(
|
||||||
|
icon: Icon(e.icon),
|
||||||
|
label: Text(
|
||||||
|
e.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
selectedIndex: _selectedIndex,
|
||||||
|
onDestinationSelected: (value) => setState(() {
|
||||||
|
_selectedIndex = value;
|
||||||
|
}),
|
||||||
|
extended: true,
|
||||||
|
leading: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 15),
|
||||||
|
child: Row(children: [
|
||||||
|
Image.asset(
|
||||||
|
"assets/spotube-logo.png",
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
Text("Spotube",
|
||||||
|
style: Theme.of(context).textTheme.headline4),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
trailing:
|
||||||
|
Consumer<SpotifyDI>(builder: (context, data, widget) {
|
||||||
|
return FutureBuilder<User>(
|
||||||
|
future: data.spotifyApi.me.get(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
var avatarImg = snapshot.data?.images?.last.url;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (avatarImg != null)
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundImage:
|
||||||
|
CachedNetworkImageProvider(avatarImg),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
snapshot.data?.displayName ?? "User's name",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.settings_outlined),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.push(MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const Settings();
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
// contents of the spotify
|
||||||
|
if (_selectedIndex == 0)
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: PagedListView(
|
||||||
|
pagingController: _pagingController,
|
||||||
|
builderDelegate: PagedChildBuilderDelegate<Category>(
|
||||||
|
itemBuilder: (context, item, index) {
|
||||||
|
return CategoryCard(item);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_selectedIndex == 2) const UserLibrary(),
|
||||||
|
if (_selectedIndex == 3) const Lyrics(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// player itself
|
||||||
|
const player.Player()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
159
lib/components/Login.dart
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:spotify/spotify.dart' hide Image;
|
||||||
|
import 'package:spotube/components/Home.dart';
|
||||||
|
import 'package:spotube/components/PageWindowTitleBar.dart';
|
||||||
|
import 'package:spotube/helpers/server_ipc.dart';
|
||||||
|
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||||
|
import 'package:spotube/provider/Auth.dart';
|
||||||
|
|
||||||
|
class Login extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_LoginState createState() => _LoginState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LoginState extends State<Login> {
|
||||||
|
String clientId = "";
|
||||||
|
String clientSecret = "";
|
||||||
|
bool _fieldError = false;
|
||||||
|
String? accessToken;
|
||||||
|
String? refreshToken;
|
||||||
|
DateTime? expiration;
|
||||||
|
|
||||||
|
handleLogin(Auth authState) async {
|
||||||
|
try {
|
||||||
|
if (clientId == "" || clientSecret == "") {
|
||||||
|
return setState(() {
|
||||||
|
_fieldError = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
final credentials = SpotifyApiCredentials(clientId, clientSecret);
|
||||||
|
final grant = SpotifyApi.authorizationCodeGrant(credentials);
|
||||||
|
const redirectUri = "http://localhost:4304/auth/spotify/callback";
|
||||||
|
|
||||||
|
final authUri = grant.getAuthorizationUrl(Uri.parse(redirectUri),
|
||||||
|
scopes: spotifyScopes);
|
||||||
|
|
||||||
|
final responseUri = await connectIpc(authUri.toString(), redirectUri);
|
||||||
|
SharedPreferences localStorage = await SharedPreferences.getInstance();
|
||||||
|
if (responseUri != null) {
|
||||||
|
final SpotifyApi spotify =
|
||||||
|
SpotifyApi.fromAuthCodeGrant(grant, responseUri);
|
||||||
|
var credentials = await spotify.getCredentials();
|
||||||
|
if (credentials.accessToken != null) {
|
||||||
|
accessToken = credentials.accessToken;
|
||||||
|
await localStorage.setString(
|
||||||
|
LocalStorageKeys.accessToken, credentials.accessToken!);
|
||||||
|
}
|
||||||
|
if (credentials.refreshToken != null) {
|
||||||
|
refreshToken = credentials.refreshToken;
|
||||||
|
await localStorage.setString(
|
||||||
|
LocalStorageKeys.refreshToken, credentials.refreshToken!);
|
||||||
|
}
|
||||||
|
if (credentials.expiration != null) {
|
||||||
|
expiration = credentials.expiration;
|
||||||
|
await localStorage.setString(LocalStorageKeys.expiration,
|
||||||
|
credentials.expiration?.toString() ?? "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await localStorage.setString(LocalStorageKeys.clientId, clientId);
|
||||||
|
await localStorage.setString(
|
||||||
|
LocalStorageKeys.clientSecret,
|
||||||
|
clientSecret,
|
||||||
|
);
|
||||||
|
authState.setAuthState(
|
||||||
|
clientId: clientId,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
accessToken: accessToken,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
expiration: expiration,
|
||||||
|
isLoggedIn: true,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print("[Login.handleLogin] $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<Auth>(
|
||||||
|
builder: (context, authState, child) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
const PageWindowTitleBar(),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
"assets/spotube-logo.png",
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
),
|
||||||
|
Text("Add your spotify credentials to get started",
|
||||||
|
style: Theme.of(context).textTheme.headline4),
|
||||||
|
const Text(
|
||||||
|
"Don't worry, any of your credentials won't be collected or shared with anyone"),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 400,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: "Spotify Client ID",
|
||||||
|
label: Text("ClientID"),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
clientId = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: "Spotify Client Secret",
|
||||||
|
label: Text("Client Secret"),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
clientSecret = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
handleLogin(authState);
|
||||||
|
},
|
||||||
|
child: const Text("Submit"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
92
lib/components/Lyrics.dart
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotube/helpers/artist-to-string.dart';
|
||||||
|
import 'package:spotube/helpers/getLyrics.dart';
|
||||||
|
import 'package:spotube/provider/Playback.dart';
|
||||||
|
import 'package:spotube/provider/UserPreferences.dart';
|
||||||
|
|
||||||
|
class Lyrics extends StatefulWidget {
|
||||||
|
const Lyrics({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Lyrics> createState() => _LyricsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LyricsState extends State<Lyrics> {
|
||||||
|
Map<String, String> _lyrics = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Playback playback = context.watch<Playback>();
|
||||||
|
UserPreferences userPreferences = context.watch<UserPreferences>();
|
||||||
|
|
||||||
|
if (playback.currentTrack != null &&
|
||||||
|
userPreferences.geniusAccessToken != null &&
|
||||||
|
playback.currentTrack!.id != _lyrics["id"]) {
|
||||||
|
getLyrics(
|
||||||
|
playback.currentTrack!.name!,
|
||||||
|
artistsToString(playback.currentTrack!.artists ?? []),
|
||||||
|
apiKey: userPreferences.geniusAccessToken,
|
||||||
|
optimizeQuery: true,
|
||||||
|
).then((lyrics) {
|
||||||
|
if (lyrics != null) {
|
||||||
|
setState(() {
|
||||||
|
_lyrics = {"lyrics": lyrics, "id": playback.currentTrack!.id!};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lyrics["lyrics"] != null && playback.currentTrack == null) {
|
||||||
|
setState(() {
|
||||||
|
_lyrics = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_lyrics["lyrics"] == null && playback.currentTrack != null) {
|
||||||
|
return const Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
playback.currentTrack?.name ?? "",
|
||||||
|
style: Theme.of(context).textTheme.headline3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
artistsToString(playback.currentTrack?.artists ?? []),
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_lyrics["lyrics"] == null && playback.currentTrack == null
|
||||||
|
? "No Track being played currently"
|
||||||
|
: _lyrics["lyrics"]!,
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Align(
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Text("Powered by genius.com"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
lib/components/PageWindowTitleBar.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mpv_dart/mpv_dart.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotube/provider/PlayerDI.dart';
|
||||||
|
|
||||||
|
class TitleBarActionButtons extends StatelessWidget {
|
||||||
|
const TitleBarActionButtons({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
MPVPlayer player = context.watch<PlayerDI>().player;
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
appWindow.minimize();
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
foregroundColor:
|
||||||
|
MaterialStateProperty.all(Theme.of(context).iconTheme.color),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.minimize_rounded)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
appWindow.maximizeOrRestore();
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
foregroundColor:
|
||||||
|
MaterialStateProperty.all(Theme.of(context).iconTheme.color),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.crop_square_rounded)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
player.stop();
|
||||||
|
appWindow.close();
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
foregroundColor:
|
||||||
|
MaterialStateProperty.all(Theme.of(context).iconTheme.color),
|
||||||
|
overlayColor: MaterialStateProperty.all(Colors.redAccent),
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close_rounded,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageWindowTitleBar extends StatelessWidget {
|
||||||
|
final Widget? leading;
|
||||||
|
final Widget? center;
|
||||||
|
const PageWindowTitleBar({Key? key, this.leading, this.center})
|
||||||
|
: super(key: key);
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WindowTitleBarBox(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (leading != null) leading!,
|
||||||
|
Expanded(child: MoveWindow(child: Center(child: center))),
|
||||||
|
const TitleBarActionButtons()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
312
lib/components/Player.dart
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/PlayerControls.dart';
|
||||||
|
import 'package:spotube/helpers/artist-to-string.dart';
|
||||||
|
import 'package:spotube/models/GlobalKeyActions.dart';
|
||||||
|
import 'package:spotube/provider/Playback.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mpv_dart/mpv_dart.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotube/provider/PlayerDI.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
class Player extends StatefulWidget {
|
||||||
|
const Player({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PlayerState createState() => _PlayerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlayerState extends State<Player> {
|
||||||
|
bool _isPlaying = false;
|
||||||
|
bool _shuffled = false;
|
||||||
|
double _duration = 0;
|
||||||
|
|
||||||
|
String? _currentPlaylistId;
|
||||||
|
|
||||||
|
double _volume = 0;
|
||||||
|
|
||||||
|
List<HotKey> _hotKeys = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
||||||
|
try {
|
||||||
|
MPVPlayer player = context.read<PlayerDI>().player;
|
||||||
|
if (!player.isRunning()) {
|
||||||
|
await player.start();
|
||||||
|
}
|
||||||
|
double volume = await player.getProperty<double>("volume");
|
||||||
|
setState(() {
|
||||||
|
_volume = volume / 100;
|
||||||
|
});
|
||||||
|
player.on(MPVEvents.paused, null, (ev, context) {
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on(MPVEvents.resumed, null, (ev, context) {
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on(MPVEvents.status, null, (ev, _) async {
|
||||||
|
Map data = ev.eventData as Map;
|
||||||
|
Playback playback = context.read<Playback>();
|
||||||
|
|
||||||
|
if (data["property"] == "media-title" && data["value"] != null) {
|
||||||
|
var track = playback.currentPlaylist?.tracks.where((track) {
|
||||||
|
String title = data["value"];
|
||||||
|
return title.contains(track.name!) &&
|
||||||
|
track.artists!
|
||||||
|
.every((artist) => title.contains(artist.name!));
|
||||||
|
});
|
||||||
|
if (track != null && track.isNotEmpty) {
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = true;
|
||||||
|
});
|
||||||
|
playback.setCurrentTrack = track.first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data["property"] == "duration" && data["value"] != null) {
|
||||||
|
setState(() {
|
||||||
|
_duration = data["value"];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
playOrPause(key) async {
|
||||||
|
_isPlaying ? await player.pause() : await player.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GlobalKeyActions> keyWithActions = [
|
||||||
|
GlobalKeyActions(
|
||||||
|
HotKey(KeyCode.space, scope: HotKeyScope.inapp),
|
||||||
|
playOrPause,
|
||||||
|
),
|
||||||
|
GlobalKeyActions(
|
||||||
|
HotKey(KeyCode.mediaPlayPause),
|
||||||
|
playOrPause,
|
||||||
|
),
|
||||||
|
GlobalKeyActions(HotKey(KeyCode.mediaTrackNext), (key) async {
|
||||||
|
await player.next();
|
||||||
|
}),
|
||||||
|
GlobalKeyActions(HotKey(KeyCode.mediaTrackPrevious), (key) async {
|
||||||
|
await player.prev();
|
||||||
|
}),
|
||||||
|
GlobalKeyActions(HotKey(KeyCode.mediaStop), (key) async {
|
||||||
|
await player.stop();
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = false;
|
||||||
|
_currentPlaylistId = null;
|
||||||
|
_duration = 0;
|
||||||
|
_shuffled = false;
|
||||||
|
});
|
||||||
|
playback.reset();
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
await Future.wait(
|
||||||
|
keyWithActions.map((e) {
|
||||||
|
return hotKeyManager.register(
|
||||||
|
e.hotKey,
|
||||||
|
keyDownHandler: e.onKeyDown,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print("[PLAYER]: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() async {
|
||||||
|
MPVPlayer player = context.read<PlayerDI>().player;
|
||||||
|
player.removeAllByEvent(MPVEvents.paused);
|
||||||
|
player.removeAllByEvent(MPVEvents.resumed);
|
||||||
|
player.removeAllByEvent(MPVEvents.status);
|
||||||
|
await Future.wait(_hotKeys.map((e) => hotKeyManager.unregister(e)));
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String playlistToStr(CurrentPlaylist playlist) {
|
||||||
|
var tracks = playlist.tracks.map((track) {
|
||||||
|
var artists = artistsToString(track.artists ?? []);
|
||||||
|
var title = track.name?.replaceAll("-", " ");
|
||||||
|
|
||||||
|
return "ytdl://ytsearch:$artists - $title";
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return tracks.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future playPlaylist(MPVPlayer player, CurrentPlaylist playlist) async {
|
||||||
|
try {
|
||||||
|
if (player.isRunning() && playlist.id != _currentPlaylistId) {
|
||||||
|
var playlistPath = "/tmp/playlist-${playlist.id}.json";
|
||||||
|
File file = File(playlistPath);
|
||||||
|
var newPlaylist = playlistToStr(playlist);
|
||||||
|
|
||||||
|
if (!await file.exists()) {
|
||||||
|
await file.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
await file.writeAsString(newPlaylist);
|
||||||
|
|
||||||
|
await player.loadPlaylist(playlistPath);
|
||||||
|
setState(() {
|
||||||
|
_currentPlaylistId = playlist.id;
|
||||||
|
_shuffled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
print("[Player]: $e");
|
||||||
|
print(stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
MPVPlayer player = context.watch<PlayerDI>().player;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
child: Consumer<Playback>(
|
||||||
|
builder: (context, playback, widget) {
|
||||||
|
if (playback.currentPlaylist != null) {
|
||||||
|
playPlaylist(player, playback.currentPlaylist!);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? albumArt = playback.currentTrack?.album?.images?.last.url;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
type: MaterialType.transparency,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
if (albumArt != null)
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: albumArt,
|
||||||
|
maxHeightDiskCache: 50,
|
||||||
|
maxWidthDiskCache: 50,
|
||||||
|
placeholder: (context, url) {
|
||||||
|
return Container(
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
color: Colors.green[400],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// title of the currently playing track
|
||||||
|
Flexible(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
playback.currentTrack?.name ?? "Not playing",
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
artistsToString(playback.currentTrack?.artists ?? []))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// controls
|
||||||
|
Flexible(
|
||||||
|
flex: 3,
|
||||||
|
child: PlayerControls(
|
||||||
|
player: player,
|
||||||
|
isPlaying: _isPlaying,
|
||||||
|
duration: _duration,
|
||||||
|
shuffled: _shuffled,
|
||||||
|
onShuffle: () {
|
||||||
|
if (!_shuffled) {
|
||||||
|
player.shuffle().then(
|
||||||
|
(value) => setState(() {
|
||||||
|
_shuffled = true;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
player.unshuffle().then(
|
||||||
|
(value) => setState(() {
|
||||||
|
_shuffled = false;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onStop: () {
|
||||||
|
setState(() {
|
||||||
|
_isPlaying = false;
|
||||||
|
_currentPlaylistId = null;
|
||||||
|
_duration = 0;
|
||||||
|
_shuffled = false;
|
||||||
|
});
|
||||||
|
playback.reset();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// add to saved tracks
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Wrap(
|
||||||
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
children: [
|
||||||
|
Consumer<SpotifyDI>(builder: (context, data, widget) {
|
||||||
|
return FutureBuilder<bool>(
|
||||||
|
future: playback.currentTrack?.id != null
|
||||||
|
? data.spotifyApi.tracks.me
|
||||||
|
.containsOne(playback.currentTrack!.id!)
|
||||||
|
: Future.value(false),
|
||||||
|
initialData: false,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
bool isLiked = snapshot.data ?? false;
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
!isLiked
|
||||||
|
? Icons.favorite_outline_rounded
|
||||||
|
: Icons.favorite_rounded,
|
||||||
|
color: isLiked ? Colors.green : null,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (!isLiked &&
|
||||||
|
playback.currentTrack?.id != null) {
|
||||||
|
data.spotifyApi.tracks.me
|
||||||
|
.saveOne(playback.currentTrack!.id!)
|
||||||
|
.then((value) => setState(() {}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
|
child: Slider.adaptive(
|
||||||
|
value: _volume,
|
||||||
|
onChanged: (value) {
|
||||||
|
player.volume(value * 100).then((_) {
|
||||||
|
setState(() {
|
||||||
|
_volume = value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
143
lib/components/PlayerControls.dart
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:mpv_dart/mpv_dart.dart';
|
||||||
|
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
||||||
|
|
||||||
|
class PlayerControls extends StatefulWidget {
|
||||||
|
final MPVPlayer player;
|
||||||
|
final bool isPlaying;
|
||||||
|
final double duration;
|
||||||
|
final bool shuffled;
|
||||||
|
final Function? onStop;
|
||||||
|
final Function? onShuffle;
|
||||||
|
const PlayerControls({
|
||||||
|
required this.player,
|
||||||
|
required this.isPlaying,
|
||||||
|
required this.duration,
|
||||||
|
required this.shuffled,
|
||||||
|
this.onShuffle,
|
||||||
|
this.onStop,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_PlayerControlsState createState() => _PlayerControlsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlayerControlsState extends State<PlayerControls> {
|
||||||
|
double currentPos = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.player.on(MPVEvents.timeposition, null, (ev, context) {
|
||||||
|
widget.player.getPercentPosition().then((value) {
|
||||||
|
setState(() {
|
||||||
|
currentPos = value / 100;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.player.removeAllByEvent(MPVEvents.timeposition);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var totalDuration = Duration(seconds: widget.duration.toInt());
|
||||||
|
var totalMinutes = zeroPadNumStr(totalDuration.inMinutes.remainder(60));
|
||||||
|
var totalSeconds = zeroPadNumStr(totalDuration.inSeconds.remainder(60));
|
||||||
|
|
||||||
|
var currentDuration =
|
||||||
|
Duration(seconds: (widget.duration * currentPos).toInt());
|
||||||
|
|
||||||
|
var currentMinutes = zeroPadNumStr(currentDuration.inMinutes.remainder(60));
|
||||||
|
var currentSeconds = zeroPadNumStr(currentDuration.inSeconds.remainder(60));
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 700),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Slider.adaptive(
|
||||||
|
value: currentPos,
|
||||||
|
onChanged: (value) async {
|
||||||
|
try {
|
||||||
|
setState(() {
|
||||||
|
currentPos = value;
|
||||||
|
});
|
||||||
|
await widget.player.goToPosition(value * widget.duration);
|
||||||
|
} catch (e) {
|
||||||
|
print("[PlayerControls]: $e");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"$currentMinutes:$currentSeconds/$totalMinutes:$totalSeconds",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.shuffle_rounded),
|
||||||
|
color:
|
||||||
|
widget.shuffled ? Theme.of(context).primaryColor : null,
|
||||||
|
onPressed: () {
|
||||||
|
widget.onShuffle?.call();
|
||||||
|
}),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.skip_previous_rounded),
|
||||||
|
onPressed: () async {
|
||||||
|
bool moved = await widget.player.prev();
|
||||||
|
if (moved) {
|
||||||
|
setState(() {
|
||||||
|
currentPos = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
widget.isPlaying
|
||||||
|
? Icons.pause_rounded
|
||||||
|
: Icons.play_arrow_rounded,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
widget.isPlaying
|
||||||
|
? await widget.player.pause()
|
||||||
|
: await widget.player.play();
|
||||||
|
}),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.skip_next_rounded),
|
||||||
|
onPressed: () async {
|
||||||
|
bool moved = await widget.player.next();
|
||||||
|
if (moved) {
|
||||||
|
setState(() {
|
||||||
|
currentPos = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.stop_rounded),
|
||||||
|
onPressed: () async {
|
||||||
|
await widget.player.stop();
|
||||||
|
widget.onStop?.call();
|
||||||
|
setState(() {
|
||||||
|
currentPos = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
128
lib/components/PlaylistCard.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/PlaylistView.dart';
|
||||||
|
import 'package:spotube/provider/Playback.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
class PlaylistCard extends StatefulWidget {
|
||||||
|
final PlaylistSimple playlist;
|
||||||
|
const PlaylistCard(this.playlist, {Key? key}) : super(key: key);
|
||||||
|
@override
|
||||||
|
_PlaylistCardState createState() => _PlaylistCardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaylistCardState extends State<PlaylistCard> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.of(context).push(MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return PlaylistView(widget.playlist);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 200),
|
||||||
|
child: Ink(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).backgroundColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
spreadRadius: 5,
|
||||||
|
color: Theme.of(context).shadowColor)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// thumbnail of the playlist
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: widget.playlist.images![0].url!,
|
||||||
|
progressIndicatorBuilder: (context, url, progress) {
|
||||||
|
return CircularProgressIndicator.adaptive(
|
||||||
|
value: progress.progress,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned.directional(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
bottom: 10,
|
||||||
|
end: 5,
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
Playback playback = context.watch<Playback>();
|
||||||
|
SpotifyDI data = context.watch<SpotifyDI>();
|
||||||
|
bool isPlaylistPlaying = playback.currentPlaylist !=
|
||||||
|
null &&
|
||||||
|
playback.currentPlaylist!.id == widget.playlist.id;
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (isPlaylistPlaying) return;
|
||||||
|
|
||||||
|
List<Track> tracks =
|
||||||
|
(widget.playlist.id != "user-liked-tracks"
|
||||||
|
? await data.spotifyApi.playlists
|
||||||
|
.getTracksByPlaylistId(
|
||||||
|
widget.playlist.id!)
|
||||||
|
.all()
|
||||||
|
: await data.spotifyApi.tracks.me.saved
|
||||||
|
.all()
|
||||||
|
.then((tracks) =>
|
||||||
|
tracks.map((e) => e.track!)))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
playback.setCurrentPlaylist = CurrentPlaylist(
|
||||||
|
tracks: tracks,
|
||||||
|
id: widget.playlist.id!,
|
||||||
|
name: widget.playlist.name!,
|
||||||
|
thumbnail: widget.playlist.images!.first.url!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
isPlaylistPlaying
|
||||||
|
? Icons.pause_rounded
|
||||||
|
: Icons.play_arrow_rounded,
|
||||||
|
),
|
||||||
|
style: ButtonStyle(
|
||||||
|
shape: MaterialStateProperty.all(
|
||||||
|
const CircleBorder(),
|
||||||
|
),
|
||||||
|
padding: MaterialStateProperty.all(
|
||||||
|
const EdgeInsets.all(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.playlist.name!,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
72
lib/components/PlaylistGenreView.dart
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/PageWindowTitleBar.dart';
|
||||||
|
import 'package:spotube/components/PlaylistCard.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
class PlaylistGenreView extends StatefulWidget {
|
||||||
|
final String genreId;
|
||||||
|
final String genreName;
|
||||||
|
final Iterable<PlaylistSimple>? playlists;
|
||||||
|
const PlaylistGenreView(
|
||||||
|
this.genreId,
|
||||||
|
this.genreName, {
|
||||||
|
this.playlists,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
@override
|
||||||
|
_PlaylistGenreViewState createState() => _PlaylistGenreViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaylistGenreViewState extends State<PlaylistGenreView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
const PageWindowTitleBar(
|
||||||
|
leading: BackButton(),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.genreName,
|
||||||
|
style: Theme.of(context).textTheme.headline4,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
Consumer<SpotifyDI>(
|
||||||
|
builder: (context, data, child) => Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: FutureBuilder<Iterable<PlaylistSimple>>(
|
||||||
|
future: widget.playlists == null
|
||||||
|
? (widget.genreId != "user-featured-playlists"
|
||||||
|
? data.spotifyApi.playlists
|
||||||
|
.getByCategoryId(widget.genreId)
|
||||||
|
.all()
|
||||||
|
: data.spotifyApi.playlists.featured.all())
|
||||||
|
: Future.value(widget.playlists),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return const Center(child: Text("Error occurred"));
|
||||||
|
}
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return const CircularProgressIndicator.adaptive();
|
||||||
|
}
|
||||||
|
return Wrap(
|
||||||
|
children: snapshot.data!
|
||||||
|
.map(
|
||||||
|
(playlist) => Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: PlaylistCard(playlist),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
230
lib/components/PlaylistView.dart
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:spotube/components/PageWindowTitleBar.dart';
|
||||||
|
import 'package:spotube/helpers/zero-pad-num-str.dart';
|
||||||
|
import 'package:spotube/provider/Playback.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/provider/SpotifyDI.dart';
|
||||||
|
|
||||||
|
class PlaylistView extends StatefulWidget {
|
||||||
|
final PlaylistSimple playlist;
|
||||||
|
const PlaylistView(this.playlist, {Key? key}) : super(key: key);
|
||||||
|
@override
|
||||||
|
_PlaylistViewState createState() => _PlaylistViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaylistViewState extends State<PlaylistView> {
|
||||||
|
List<TableRow> trackToTableRow(List<Track> tracks) {
|
||||||
|
return tracks.asMap().entries.map((track) {
|
||||||
|
var thumbnailUrl = track.value.album?.images?.last.url;
|
||||||
|
var duration =
|
||||||
|
"${track.value.duration?.inMinutes.remainder(60)}:${zeroPadNumStr(track.value.duration?.inSeconds.remainder(60) ?? 0)}";
|
||||||
|
return (TableRow(
|
||||||
|
children: [
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
(track.key + 1).toString(),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (thumbnailUrl != null)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
placeholder: (context, url) {
|
||||||
|
return Container(
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
color: Colors.green[300],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
imageUrl: thumbnailUrl,
|
||||||
|
maxHeightDiskCache: 40,
|
||||||
|
maxWidthDiskCache: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Flexible(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
track.value.name ?? "",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 17,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
(track.value.artists ?? [])
|
||||||
|
.map((e) => e.name)
|
||||||
|
.join(", "),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
track.value.album?.name ?? "",
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TableCell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
duration,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<SpotifyDI>(builder: (_, data, __) {
|
||||||
|
return Scaffold(
|
||||||
|
body: FutureBuilder<Iterable<Track>>(
|
||||||
|
future: widget.playlist.id != "user-liked-tracks"
|
||||||
|
? data.spotifyApi.playlists
|
||||||
|
.getTracksByPlaylistId(widget.playlist.id)
|
||||||
|
.all()
|
||||||
|
: data.spotifyApi.tracks.me.saved
|
||||||
|
.all()
|
||||||
|
.then((tracks) => tracks.map((e) => e.track!)),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
List<Track> tracks = snapshot.data?.toList() ?? [];
|
||||||
|
TextStyle tableHeadStyle =
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16);
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
PageWindowTitleBar(
|
||||||
|
leading: Row(
|
||||||
|
children: [
|
||||||
|
// nav back
|
||||||
|
const BackButton(),
|
||||||
|
// heart playlist
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.favorite_outline_rounded),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
// play playlist
|
||||||
|
Consumer<Playback>(
|
||||||
|
builder: (context, playback, widget) {
|
||||||
|
var isPlaylistPlaying =
|
||||||
|
playback.currentPlaylist?.id ==
|
||||||
|
this.widget.playlist.id;
|
||||||
|
return IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
isPlaylistPlaying
|
||||||
|
? Icons.stop_rounded
|
||||||
|
: Icons.play_arrow_rounded,
|
||||||
|
),
|
||||||
|
onPressed: snapshot.hasData
|
||||||
|
? () {
|
||||||
|
if (!isPlaylistPlaying) {
|
||||||
|
playback.setCurrentPlaylist =
|
||||||
|
CurrentPlaylist(
|
||||||
|
tracks: tracks,
|
||||||
|
id: this.widget.playlist.id!,
|
||||||
|
name: this.widget.playlist.name!,
|
||||||
|
thumbnail: this
|
||||||
|
.widget
|
||||||
|
.playlist
|
||||||
|
.images![0]
|
||||||
|
.url!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Text(widget.playlist.name!,
|
||||||
|
style: Theme.of(context).textTheme.headline4),
|
||||||
|
),
|
||||||
|
snapshot.hasError
|
||||||
|
? const Center(child: Text("Error occurred"))
|
||||||
|
: !snapshot.hasData
|
||||||
|
? const Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator.adaptive()),
|
||||||
|
)
|
||||||
|
: Expanded(
|
||||||
|
child: Scrollbar(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Table(
|
||||||
|
columnWidths: const {
|
||||||
|
0: FixedColumnWidth(40),
|
||||||
|
1: FlexColumnWidth(),
|
||||||
|
2: FlexColumnWidth(),
|
||||||
|
3: FixedColumnWidth(45),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
TableRow(
|
||||||
|
children: [
|
||||||
|
TableCell(
|
||||||
|
child: Text(
|
||||||
|
"#",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: tableHeadStyle,
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
child: Text(
|
||||||
|
"Title",
|
||||||
|
style: tableHeadStyle,
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
child: Text(
|
||||||
|
"Album",
|
||||||
|
style: tableHeadStyle,
|
||||||
|
)),
|
||||||
|
TableCell(
|
||||||
|
child: Text(
|
||||||
|
"Time",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: tableHeadStyle,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
...trackToTableRow(tracks),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|