mirror of
https://github.com/simon-ding/polaris.git
synced 2026-02-25 05:00:49 +08:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae611943c3 | ||
|
|
4fd11540cd | ||
|
|
587a28127b | ||
|
|
05ae58030c | ||
|
|
f1c4e306f4 | ||
|
|
949b6e5188 | ||
|
|
0d4b453d0a | ||
|
|
bce4d93ab1 | ||
|
|
36b72e6461 | ||
|
|
62417727f9 | ||
|
|
03f72b9d86 | ||
|
|
c17cf750e5 | ||
|
|
b176253fc4 | ||
|
|
1e2d8b8520 | ||
|
|
3739f2c960 | ||
|
|
bb6da47efb | ||
|
|
c28373bde1 | ||
|
|
8ce7045466 | ||
|
|
0b1bd8226d | ||
|
|
e67413cec2 | ||
|
|
2da02fa706 | ||
|
|
bc50dd888a | ||
|
|
0305c0709d | ||
|
|
5e9e85206e | ||
|
|
3babb9f5c7 | ||
|
|
c9928f10ce | ||
|
|
a2da1e7479 | ||
|
|
038f643ce3 | ||
|
|
cf2fa90d5d | ||
|
|
677923ea8a | ||
|
|
1192d00fe0 | ||
|
|
b317636a8a | ||
|
|
0e6465593b | ||
|
|
2cb8a5b6fb |
30
README.md
30
README.md
@@ -1,4 +1,10 @@
|
||||
# Polaris
|
||||
|
||||

|
||||
|
||||
|
||||
<h1 align="center">Polaris</h1>
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||
@@ -6,11 +12,10 @@
|
||||

|
||||
|
||||
|
||||
Polaris 是一个电视剧和电影的追踪下载软件。对动漫日剧美剧都有良好的匹配,支持webdav或者本地存储。
|
||||
**Polaris 是一个电视剧和电影的追踪下载软件。对动漫日剧美剧都有良好的匹配,支持webdav或者本地存储。**
|
||||
|
||||
</div>
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
交流群: https://t.me/+8R2nzrlSs2JhMDgx
|
||||
|
||||
@@ -40,6 +45,13 @@ Polaris 是一个电视剧和电影的追踪下载软件。对动漫日剧美剧
|
||||
- [ ] 手机客户端
|
||||
|
||||
|
||||
## 截图
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## 运行原理
|
||||
|
||||
Polaris本身不提供任何资源,要使其能正常工作,需要同时安装一个下载客户端(transmission)和一个索引客户端(jackett)。
|
||||
@@ -48,13 +60,17 @@ Polaris通过索引客户端查询相关的BT/PT站点,然后把查到的资
|
||||
|
||||

|
||||
|
||||
## 对比 sonarr/radarr
|
||||
<!-- ## 对比 sonarr/radarr
|
||||
* 更好的中文支持
|
||||
* 对于动漫、日剧的良好支持,配合国内站点基本能匹配上对应资源
|
||||
* 支持 webdav 后端存储,可以配合 alist 或者阿里云来实现下载后实时传到云上的功能。这样外出就可以不依靠家里的宽带来看电影了,或者实现个轻 NAS 功能,下载功能放在本地,数据放在云盘
|
||||
* golang 实现后端,相比于 .NET 更节省资源
|
||||
* 一个程序同时实现了电影、电视剧功能,不需要装两个程序
|
||||
* 当然 sonarr/radarr 也是非常优秀的开源项目,目前 Polaris 功能还没有 sonarr/radarr 丰富
|
||||
* 当然 sonarr/radarr 也是非常优秀的开源项目,目前 Polaris 功能还没有 sonarr/radarr 丰富 -->
|
||||
|
||||
|
||||
## Stargazers over time
|
||||
[](https://starchart.cc/simon-ding/polaris)
|
||||
|
||||
-------------
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
SetttingSizeLimiter = "size_limiter"
|
||||
SettingTvNamingFormat = "tv_naming_format"
|
||||
SettingMovieNamingFormat = "movie_naming_format"
|
||||
SettingProwlarrInfo = "prowlarr_info"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -60,3 +61,9 @@ type Limiter struct {
|
||||
Max int `json:"max"`
|
||||
Min int `json:"min"`
|
||||
}
|
||||
|
||||
type ProwlarrSetting struct {
|
||||
Disabled bool `json:"disabled"`
|
||||
ApiKey string `json:"api_key"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
21
db/db.go
21
db/db.go
@@ -660,3 +660,24 @@ func (c *Client) CleanAllDanglingEpisodes() error {
|
||||
func (c *Client) AddBlacklistItem(item *ent.Blacklist) error {
|
||||
return c.ent.Blacklist.Create().SetType(item.Type).SetValue(item.Value).SetNotes(item.Notes).Exec(context.Background())
|
||||
}
|
||||
|
||||
|
||||
func (c *Client) GetProwlarrSetting() (*ProwlarrSetting, error) {
|
||||
s := c.GetSetting(SettingProwlarrInfo)
|
||||
if s == "" {
|
||||
return nil, errors.New("prowlarr setting not set")
|
||||
}
|
||||
var se ProwlarrSetting
|
||||
if err := json.Unmarshal([]byte(s), &se); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &se, nil
|
||||
}
|
||||
|
||||
func (c *Client) SaveProwlarrSetting(se *ProwlarrSetting) error {
|
||||
data, err := json.Marshal(se)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetSetting(SettingProwlarrInfo, string(data))
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.4 MiB After Width: | Height: | Size: 843 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
要正确使用此程序,需要配置好以下设置:
|
||||
|
||||
### TMDB设置
|
||||
### 1. TMDB设置
|
||||
|
||||
1. 因为此程序需要使用到 TMDB 的数据,使用此程序首先要申请一个 TMDB 的 Api Key. 申请教程请 google [tmdb api key申请](https://www.google.com/search?q=tmdb+api+key%E7%94%B3%E8%AF%B7)
|
||||
|
||||
@@ -10,35 +10,16 @@
|
||||
|
||||
**注意:** TMDB可能需要翻墙才能使用,参考 [TMDB 访问问题](./tmdb.md)
|
||||
|
||||
### 索引器
|
||||
### 2. 索引器
|
||||
|
||||
索引器是资源提供者,目前支持 torznab 协议,意味着 polarr 或者 jackett 都可以支持。请自行部署相关程序,或者使用的 docker compose 配置一起拉起
|
||||
使用配置页面索引器配置或者prowlarr设置,其中一个即可。
|
||||
|
||||
推荐使用 linuxserver 的镜像:https://docs.linuxserver.io/images/docker-jackett/
|
||||
#### jackett配置参考 [jackett](./jackett.md)
|
||||
|
||||
#### 索引器配置
|
||||
#### prowlarr设置
|
||||
|
||||
索引器配置这里以 jackett 为例。使用默认 docker compose 配置拉起后以 http://< ip >:9117 可访问 jackett 的主页。
|
||||
|
||||
1. 打开 jackett 主页后,点击页面上面的 Add indexer,会出现 BT/PT 站点列表,选择你需要的站点点击+号添加。如果是PT,请自行配置好相关配置
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
2. 添加后主页即会显示相应的BT/PT站点,点击 *Copy Torznab Feed* 即得到了我们需要的地址
|
||||
|
||||

|
||||
|
||||
3. 回到我们的主程序 Polaris 当中,点击 *设置 -> 索引器设置* -> 点击+号增加新的索引器,输入一个名称,拷贝我们第2步得到的地址到地址栏
|
||||
|
||||

|
||||
|
||||
4. 选相框中我们可以看到,还需要一个 API Key,我们回到 Jackett 中,在页面右上角,复制我们需要的 API Key:
|
||||

|
||||
|
||||
5. 恭喜!你已经成功完成了索引器配置。如需要更多的站点,请重复相同的操作完成配置
|
||||
1) 取得prowlarr的url和api key, api key在 *Prowlarr 设置 -> 通用 -> API 密钥* 处取得
|
||||
2) 对应参数填到 polaris程序,*设置->prowlarr设置*当中
|
||||
|
||||
### 下载器
|
||||
|
||||
|
||||
21
doc/jackett.md
Normal file
21
doc/jackett.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## jackett 索引器配置
|
||||
|
||||
1. 打开 jackett 主页后,点击页面上面的 Add indexer,会出现 BT/PT 站点列表,选择你需要的站点点击+号添加。如果是PT,请自行配置好相关配置
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
2. 添加后主页即会显示相应的BT/PT站点,点击 *Copy Torznab Feed* 即得到了我们需要的地址
|
||||
|
||||

|
||||
|
||||
3. 回到我们的主程序 Polaris 当中,点击 *设置 -> 索引器设置* -> 点击+号增加新的索引器,输入一个名称,拷贝我们第2步得到的地址到地址栏
|
||||
|
||||

|
||||
|
||||
4. 选相框中我们可以看到,还需要一个 API Key,我们回到 Jackett 中,在页面右上角,复制我们需要的 API Key:
|
||||

|
||||
|
||||
5. 恭喜!你已经成功完成了索引器配置。如需要更多的站点,请重复相同的操作完成配置
|
||||
@@ -1,8 +1,8 @@
|
||||
# 快速开始
|
||||
|
||||
## 安装 Polaris
|
||||
## 1. 安装 Polaris
|
||||
|
||||
### Docker Compose 方式安装
|
||||
### 1.1 Docker Compose 方式安装
|
||||
|
||||
最简单使用本程序的方式是通过docker compose,下面内容保存成 docker-compose.yml,然后执行 docker compose up -d, 即可拉起程序。
|
||||
|
||||
@@ -12,18 +12,18 @@ services:
|
||||
image: ghcr.io/simon-ding/polaris:latest
|
||||
restart: always
|
||||
environment:
|
||||
- PUID=99
|
||||
- PGID=100
|
||||
- TZ=Asia/Shanghai
|
||||
- PUID=99 #程序运行的用户UID
|
||||
- PGID=100 #程序运行的用户GID
|
||||
- TZ=Asia/Shanghai #时区
|
||||
volumes:
|
||||
- <配置文件路径>:/app/data #程序配置文件路径
|
||||
- <下载路径>:/downloads #下载路径,需要和下载客户端配置一致
|
||||
- <媒体文件路径>:/data #媒体数据存储路径,也可以启动自己配置webdav存储
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 8080:8080 #端口映射,冒号前的端口可自行改为需要的
|
||||
```
|
||||
|
||||
### Docker 方式安装
|
||||
### 1.2 Docker 方式安装
|
||||
|
||||
也可以通过原始 docker 命令的方式安装 Polaris:
|
||||
|
||||
@@ -40,19 +40,19 @@ docker run -d \
|
||||
ghcr.io/simon-ding/polaris:latest
|
||||
```
|
||||
|
||||
### Unraid 安装
|
||||
### 1.3 Unraid 安装
|
||||
|
||||
参考下图进行配置
|
||||
|
||||

|
||||
|
||||
### 访问
|
||||
### 1.4 访问
|
||||
拉起之后访问 http://< ip >:8080 即可访问 Polaris 的主页:
|
||||
|
||||

|
||||
|
||||
|
||||
## 安装下载客户端
|
||||
## 2. 安装下载客户端
|
||||
|
||||
Polaris 需要下载客户端的配合使用,目前支持 Transmission 和 Qbittorrent。推荐使用linuxserver镜像进行安装
|
||||
|
||||
@@ -62,16 +62,18 @@ Polaris 需要下载客户端的配合使用,目前支持 Transmission 和 Qbi
|
||||
|
||||
需要注意的是下载客户端内 /downloads 路径的映射地址要和 Polaris的/downloads路径映射保持一致。也就是说他俩都要映射到同一路径。
|
||||
|
||||
## 安装 Jackett
|
||||
Polaris 如果要正常工作,还需要一个索引客户端的支持,目前支持jackett索引客户端。
|
||||
## 3. 安装 Jackett/Prowlarr
|
||||
|
||||
Polaris 如果要正常工作,还需要一个索引客户端的支持,目前支持jackett/prowlarr索引客户端。推荐使用prowlarr,设置更简单
|
||||
|
||||
安装方式见:
|
||||
|
||||
* [linuxserver/jackett](https://docs.linuxserver.io/images/docker-jackett/)
|
||||
* [linuxserver/prowlarr](https://docs.linuxserver.io/images/docker-prowlarr/)
|
||||
|
||||
|
||||
|
||||
## 联合安装
|
||||
## 4. 联合安装
|
||||
|
||||
如果觉得一个个安装麻烦,也可以使用下面docker compose文件,一键拉起所有组件
|
||||
|
||||
@@ -125,7 +127,7 @@ docker compose up -d
|
||||
```
|
||||
|
||||
|
||||
## 配置
|
||||
## 5. 配置
|
||||
|
||||
详细配置请看 [配置篇](./configuration.md)
|
||||
|
||||
|
||||
1
go.mod
1
go.mod
@@ -21,6 +21,7 @@ require (
|
||||
github.com/ncruces/go-sqlite3 v0.18.4
|
||||
github.com/nikoksr/notify v1.0.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golift.io/starr v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
10
go.sum
10
go.sum
@@ -216,8 +216,6 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -251,8 +249,6 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/nikoksr/notify v1.0.0 h1:qe9/6FRsWdxBgQgWcpvQ0sv8LRGJZDpRB4TkL2uNdO8=
|
||||
github.com/nikoksr/notify v1.0.0/go.mod h1:hPaaDt30d6LAA7/5nb0e48Bp/MctDfycCSs8VEgN29I=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
@@ -310,8 +306,6 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
@@ -453,10 +447,10 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golift.io/starr v1.0.0 h1:IDSaSL+ZYxdLT/Lg//dg/iwZ39LHO3D5CmbLCOgSXbI=
|
||||
golift.io/starr v1.0.0/go.mod h1:xnUwp4vK62bDvozW9QHUYc08m6kjwaZnGw3Db65fQHw=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
||||
@@ -37,6 +37,7 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var items []importlist.Item
|
||||
doc.Find("div[class=doulist-item]").Each(func(i int, selection *goquery.Selection) {
|
||||
titleDiv := selection.Find("div[class=title]")
|
||||
link := titleDiv.Find("div>a")
|
||||
@@ -64,18 +65,26 @@ func ParseDoulist(doulistUrl string) (*importlist.Response, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err := parseDetailPage(strings.TrimSpace(href))
|
||||
if err != nil {
|
||||
log.Errorf("get detail page: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
item := importlist.Item{
|
||||
Title: strings.TrimSpace(link.Text()),
|
||||
Year: year,
|
||||
}
|
||||
items = append(items, item)
|
||||
_ = item
|
||||
println(link.Text(), href)
|
||||
//println(link.Text(), href)
|
||||
})
|
||||
return nil, nil
|
||||
|
||||
return &importlist.Response{Items: items}, nil
|
||||
}
|
||||
|
||||
func parseDetailPage(url string) (string, error) {
|
||||
println(url)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -95,6 +104,14 @@ func parseDetailPage(url string) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
doc.Find("div[class='subject clearfix']").Each(func(i int, se *goquery.Selection) {
|
||||
println(se.Text())
|
||||
se.Children().Get(1)
|
||||
imdb := se.Find("div[class='info']").First().Children().Last()
|
||||
println(imdb.Text())
|
||||
})
|
||||
|
||||
_ = doc
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
func TestParseDoulist(t *testing.T) {
|
||||
r, err := ParseDoulist("https://www.douban.com/doulist/166422/")
|
||||
r, err := ParseDoulist("https://www.douban.com/doulist/81580/")
|
||||
log.Info(r, err)
|
||||
}
|
||||
|
||||
77
pkg/prowlarr/prowlarr.go
Normal file
77
pkg/prowlarr/prowlarr.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package prowlarr
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"polaris/db"
|
||||
"polaris/ent"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golift.io/starr"
|
||||
"golift.io/starr/prowlarr"
|
||||
)
|
||||
|
||||
type ProwlarrSupportType string
|
||||
|
||||
const (
|
||||
TV ProwlarrSupportType = "tv"
|
||||
Movie ProwlarrSupportType = "movie"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
p *prowlarr.Prowlarr
|
||||
apiKey string
|
||||
url string
|
||||
}
|
||||
|
||||
func New(apiKey, url string) *Client {
|
||||
c := starr.New(apiKey, url, 10*time.Second)
|
||||
p := prowlarr.New(c)
|
||||
return &Client{p: p, apiKey: apiKey, url: url}
|
||||
}
|
||||
|
||||
func (c *Client) GetIndexers(t ProwlarrSupportType) ([]*db.TorznabInfo, error) {
|
||||
ins, err := c.p.GetIndexers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var indexers []*db.TorznabInfo
|
||||
for _, in := range ins {
|
||||
if !in.Enable {
|
||||
continue
|
||||
}
|
||||
if t == "tv" && len(in.Capabilities.TvSearchParams) == 0 { //no tv resource in this indexer
|
||||
continue
|
||||
} else if t == "movie" && len(in.Capabilities.MovieSearchParams) == 0 { //no movie resource in this indexer
|
||||
continue
|
||||
}
|
||||
seedRatio := 0.0
|
||||
for _, f := range in.Fields {
|
||||
if f.Name == "torrentBaseSettings.seedRatio" && f.Value != nil {
|
||||
if r, ok := f.Value.(float64); ok {
|
||||
seedRatio = r
|
||||
}
|
||||
}
|
||||
}
|
||||
setting := db.TorznabSetting{
|
||||
URL: fmt.Sprintf("%s/%d/api", strings.TrimSuffix(c.url, "/"), in.ID),
|
||||
ApiKey: c.apiKey,
|
||||
}
|
||||
data, _ := json.Marshal(&setting)
|
||||
|
||||
entIndexer := ent.Indexers{
|
||||
Name: in.Name,
|
||||
Implementation: "torznab",
|
||||
Priority: 128 - int(in.Priority),
|
||||
SeedRatio: float32(seedRatio),
|
||||
Settings: string(data),
|
||||
}
|
||||
|
||||
indexers = append(indexers, &db.TorznabInfo{
|
||||
Indexers: &entIndexer,
|
||||
TorznabSetting: setting,
|
||||
})
|
||||
}
|
||||
return indexers, nil
|
||||
}
|
||||
13
pkg/prowlarr/prowlarr_test.go
Normal file
13
pkg/prowlarr/prowlarr_test.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package prowlarr
|
||||
|
||||
import (
|
||||
"polaris/log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test111(t *testing.T) {
|
||||
c := New("", "http://10.0.0.8:9696/")
|
||||
apis , err := c.GetIndexers("tv")
|
||||
log.Infof("errors: %v", err)
|
||||
log.Infof("indexers: %+v", apis[0])
|
||||
}
|
||||
@@ -85,6 +85,12 @@ func (r *Response) ToResults(indexer *db.TorznabInfo) []Result {
|
||||
// log.Warnf("converting link to magnet error, error: %v, link: %v", err, item.Link)
|
||||
// continue
|
||||
// }
|
||||
imdb := ""
|
||||
if item.GetAttr("imdbid") != "" {
|
||||
imdb = item.GetAttr("imdbid")
|
||||
} else if item.GetAttr("imdb") != "" {
|
||||
imdb = item.GetAttr("imdb")
|
||||
}
|
||||
r := Result{
|
||||
Name: item.Title,
|
||||
Link: item.Link,
|
||||
@@ -92,7 +98,7 @@ func (r *Response) ToResults(indexer *db.TorznabInfo) []Result {
|
||||
Seeders: mustAtoI(item.GetAttr("seeders")),
|
||||
Peers: mustAtoI(item.GetAttr("peers")),
|
||||
Category: mustAtoI(item.GetAttr("category")),
|
||||
ImdbId: item.GetAttr("imdbid"),
|
||||
ImdbId: imdb,
|
||||
DownloadVolumeFactor: tryParseFloat(item.GetAttr("downloadvolumefactor")),
|
||||
UploadVolumeFactor: tryParseFloat(item.GetAttr("uploadvolumefactor")),
|
||||
Source: indexer.Name,
|
||||
|
||||
@@ -44,7 +44,7 @@ func (c *Client) registerCronJob(name string, cron string, f func() error) {
|
||||
}
|
||||
|
||||
func (c *Client) Init() {
|
||||
c.reloadTasks()
|
||||
go c.reloadTasks()
|
||||
c.addSysCron()
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ func (c *Client) reloadTasks() {
|
||||
}
|
||||
|
||||
}
|
||||
log.Infof("------ task reloading done ------")
|
||||
}
|
||||
|
||||
func (c *Client) GetDownloadClient() (pkg.Downloader, *ent.DownloadClients, error) {
|
||||
|
||||
@@ -36,6 +36,7 @@ func (c *Client) addSysCron() {
|
||||
return true
|
||||
})
|
||||
c.cron.Start()
|
||||
log.Infof("--------- add cron jobs done --------")
|
||||
}
|
||||
|
||||
func (c *Client) mustAddCron(spec string, cmd func()) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"polaris/ent/media"
|
||||
"polaris/log"
|
||||
"polaris/pkg/metadata"
|
||||
"polaris/pkg/prowlarr"
|
||||
"polaris/pkg/torznab"
|
||||
"slices"
|
||||
"sort"
|
||||
@@ -32,7 +33,7 @@ func SearchTvSeries(db1 *db.Client, param *SearchParam) ([]torznab.Result, error
|
||||
}
|
||||
log.Debugf("check tv series %s, season %d, episode %v", series.NameEn, param.SeasonNum, param.Episodes)
|
||||
|
||||
res := searchWithTorznab(db1, series.NameEn, series.NameCn, series.OriginalName)
|
||||
res := searchWithTorznab(db1, prowlarr.TV, series.NameEn, series.NameCn, series.OriginalName)
|
||||
|
||||
var filtered []torznab.Result
|
||||
for _, r := range res {
|
||||
@@ -136,7 +137,7 @@ func torrentSizeOk(detail *db.MediaDetails, torrentSize int, param *SearchParam)
|
||||
}
|
||||
}
|
||||
}
|
||||
return torrentSize > defaultMinSize * multiplier
|
||||
return torrentSize > defaultMinSize*multiplier
|
||||
}
|
||||
|
||||
func seasonEpisodeCount(detail *db.MediaDetails, seasonNum int) int {
|
||||
@@ -170,9 +171,9 @@ func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) {
|
||||
return nil, errors.New("no media found of id")
|
||||
}
|
||||
|
||||
res := searchWithTorznab(db1, movieDetail.NameEn, movieDetail.NameCn, movieDetail.OriginalName)
|
||||
res := searchWithTorznab(db1, prowlarr.Movie, movieDetail.NameEn, movieDetail.NameCn, movieDetail.OriginalName)
|
||||
if movieDetail.Extras.IsJav() {
|
||||
res1 := searchWithTorznab(db1, movieDetail.Extras.JavId)
|
||||
res1 := searchWithTorznab(db1, prowlarr.Movie, movieDetail.Extras.JavId)
|
||||
res = append(res, res1...)
|
||||
}
|
||||
|
||||
@@ -226,10 +227,21 @@ func SearchMovie(db1 *db.Client, param *SearchParam) ([]torznab.Result, error) {
|
||||
|
||||
}
|
||||
|
||||
func searchWithTorznab(db *db.Client, queries ...string) []torznab.Result {
|
||||
func searchWithTorznab(db *db.Client, t prowlarr.ProwlarrSupportType, queries ...string) []torznab.Result {
|
||||
|
||||
var res []torznab.Result
|
||||
allTorznab := db.GetAllTorznabInfo()
|
||||
|
||||
p, err := db.GetProwlarrSetting()
|
||||
if err == nil && !p.Disabled { //prowlarr exists
|
||||
c := prowlarr.New(p.ApiKey, p.URL)
|
||||
all, err := c.GetIndexers(t)
|
||||
if err != nil {
|
||||
log.Warnf("get prowlarr all indexer error: %v", err)
|
||||
} else {
|
||||
allTorznab = append(allTorznab, all...)
|
||||
}
|
||||
}
|
||||
resChan := make(chan []torznab.Result)
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
||||
@@ -175,6 +175,10 @@ func (s *Server) DownloadAll(c *gin.Context) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert")
|
||||
}
|
||||
return s.downloadAllEpisodes(id)
|
||||
}
|
||||
|
||||
func (s *Server) downloadAllEpisodes(id int) (interface{}, error) {
|
||||
m, err := s.db.GetMedia(id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get media")
|
||||
@@ -186,3 +190,27 @@ func (s *Server) DownloadAll(c *gin.Context) (interface{}, error) {
|
||||
|
||||
return []string{name}, err
|
||||
}
|
||||
|
||||
func (s *Server) DownloadAllTv(c *gin.Context) (interface{}, error) {
|
||||
tvs := s.db.GetMediaWatchlist(media.MediaTypeTv)
|
||||
var allNames []string
|
||||
for _, tv := range tvs {
|
||||
names, err := s.downloadAllEpisodes(tv.ID)
|
||||
if err == nil {
|
||||
allNames = append(allNames, names.([]string)...)
|
||||
}
|
||||
}
|
||||
return allNames, nil
|
||||
}
|
||||
|
||||
func (s *Server) DownloadAllMovies(c *gin.Context) (interface{}, error) {
|
||||
movies := s.db.GetMediaWatchlist(media.MediaTypeMovie)
|
||||
var allNames []string
|
||||
for _, mv := range movies {
|
||||
names, err := s.downloadAllEpisodes(mv.ID)
|
||||
if err == nil {
|
||||
allNames = append(allNames, names.([]string)...)
|
||||
}
|
||||
}
|
||||
return allNames, nil
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ func (s *Server) Serve() error {
|
||||
setting.POST("/parse/movie", HttpHandler(s.ParseMovie))
|
||||
setting.POST("/monitoring", HttpHandler(s.ChangeEpisodeMonitoring))
|
||||
setting.POST("/cron/trigger", HttpHandler(s.TriggerCronJob))
|
||||
setting.GET("/prowlarr", HttpHandler(s.GetProwlarrSetting))
|
||||
setting.POST("/prowlarr", HttpHandler(s.SaveProwlarrSetting))
|
||||
}
|
||||
activity := api.Group("/activity")
|
||||
{
|
||||
@@ -94,6 +96,8 @@ func (s *Server) Serve() error {
|
||||
tv.GET("/suggest/tv/:tmdb_id", HttpHandler(s.SuggestedSeriesFolderName))
|
||||
tv.GET("/suggest/movie/:tmdb_id", HttpHandler(s.SuggestedMovieFolderName))
|
||||
tv.GET("/downloadall/:id", HttpHandler(s.DownloadAll))
|
||||
tv.GET("/download/tv", HttpHandler(s.DownloadAllTv))
|
||||
tv.GET("/download/movie", HttpHandler(s.DownloadAllMovies))
|
||||
}
|
||||
indexer := api.Group("/indexer")
|
||||
{
|
||||
@@ -128,6 +132,7 @@ func (s *Server) Serve() error {
|
||||
importlist.POST("/add", HttpHandler(s.addImportlist))
|
||||
importlist.DELETE("/delete", HttpHandler(s.deleteImportList))
|
||||
}
|
||||
log.Infof("----------- Polaris Server Successfully Started ------------")
|
||||
|
||||
return s.r.Run(":8080")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"polaris/ent"
|
||||
"polaris/ent/downloadclients"
|
||||
"polaris/log"
|
||||
"polaris/pkg/prowlarr"
|
||||
"polaris/pkg/qbittorrent"
|
||||
"polaris/pkg/torznab"
|
||||
"polaris/pkg/transmission"
|
||||
@@ -104,6 +105,7 @@ func (s *Server) SetSetting(c *gin.Context) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
func (s *Server) GetSetting(c *gin.Context) (interface{}, error) {
|
||||
tmdb := s.db.GetSetting(db.SettingTmdbApiKey)
|
||||
downloadDir := s.db.GetSetting(db.SettingDownloadDir)
|
||||
@@ -303,3 +305,28 @@ func (s *Server) TriggerCronJob(c *gin.Context) (interface{}, error) {
|
||||
}
|
||||
return "success", nil
|
||||
}
|
||||
|
||||
func (s *Server) GetProwlarrSetting(c *gin.Context) (interface{}, error) {
|
||||
se, err :=s.db.GetProwlarrSetting()
|
||||
if err != nil {
|
||||
return &db.ProwlarrSetting{}, nil
|
||||
}
|
||||
return se, nil
|
||||
}
|
||||
func (s *Server) SaveProwlarrSetting(c *gin.Context) (interface{}, error) {
|
||||
var in db.ProwlarrSetting
|
||||
if err := c.ShouldBindJSON(&in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !in.Disabled {
|
||||
client := prowlarr.New(in.ApiKey, in.URL)
|
||||
if _, err := client.GetIndexers(prowlarr.TV); err != nil {
|
||||
return nil, errors.Wrap(err, "connect to prowlarr error")
|
||||
}
|
||||
}
|
||||
err := s.db.SaveProwlarrSetting(&in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return "success", nil
|
||||
}
|
||||
|
||||
BIN
ui/assets/wechat.jpg
Normal file
BIN
ui/assets/wechat.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
@@ -148,7 +148,7 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
|
||||
},
|
||||
));
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
})
|
||||
],
|
||||
|
||||
230
ui/lib/main.dart
230
ui/lib/main.dart
@@ -49,76 +49,79 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var padding = isSmallScreen(context) ? 5.0 : 20.0;
|
||||
// GoRouter configuration
|
||||
final shellRoute = ShellRoute(
|
||||
builder: (BuildContext context, GoRouterState state, Widget child) {
|
||||
return SelectionArea(
|
||||
child: MainSkeleton(
|
||||
body: Padding(
|
||||
padding: EdgeInsets.only(left: padding, right: padding, top: 5, bottom: 5),
|
||||
child: child),
|
||||
),
|
||||
final shellRoute = StatefulShellRoute.indexedStack(
|
||||
builder: (BuildContext context, GoRouterState state,
|
||||
StatefulNavigationShell navigationShell) {
|
||||
return MainSkeleton(
|
||||
body: navigationShell,
|
||||
);
|
||||
},
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: "/",
|
||||
redirect: (context, state) => WelcomePage.routeTv,
|
||||
),
|
||||
GoRoute(
|
||||
path: WelcomePage.routeTv,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const WelcomePage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: WelcomePage.routeMoivie,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const WelcomePage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: TvDetailsPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: TvDetailsPage(seriesId: state.pathParameters['id']!)),
|
||||
),
|
||||
GoRoute(
|
||||
path: MovieDetailsPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: MovieDetailsPage(id: state.pathParameters['id']!)),
|
||||
),
|
||||
GoRoute(
|
||||
path: SearchPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: SearchPage(query: state.uri.queryParameters["query"])),
|
||||
),
|
||||
GoRoute(
|
||||
path: SystemSettingsPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: const SystemSettingsPage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: ActivityPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const ActivityPage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: SystemPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const SystemPage()),
|
||||
)
|
||||
branches: [
|
||||
StatefulShellBranch(initialLocation: WelcomePage.routeTv, routes: [
|
||||
GoRoute(
|
||||
path: WelcomePage.routeTv,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const WelcomePage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: TvDetailsPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: TvDetailsPage(seriesId: state.pathParameters['id']!)),
|
||||
),
|
||||
GoRoute(
|
||||
path: SearchPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: SearchPage(query: state.uri.queryParameters["query"])),
|
||||
),
|
||||
]),
|
||||
StatefulShellBranch(initialLocation: WelcomePage.routeMoivie, routes: [
|
||||
GoRoute(
|
||||
path: WelcomePage.routeMoivie,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const WelcomePage()),
|
||||
),
|
||||
GoRoute(
|
||||
path: MovieDetailsPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: MovieDetailsPage(id: state.pathParameters['id']!)),
|
||||
),
|
||||
]),
|
||||
StatefulShellBranch(routes: [
|
||||
GoRoute(
|
||||
path: ActivityPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const ActivityPage()),
|
||||
),
|
||||
]),
|
||||
StatefulShellBranch(routes: [
|
||||
GoRoute(
|
||||
path: SystemSettingsPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context,
|
||||
state: state,
|
||||
child: const SystemSettingsPage()),
|
||||
),
|
||||
]),
|
||||
StatefulShellBranch(routes: [
|
||||
GoRoute(
|
||||
path: SystemPage.route,
|
||||
pageBuilder: (context, state) => buildPageWithDefaultTransition(
|
||||
context: context, state: state, child: const SystemPage()),
|
||||
)
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
final router = GoRouter(
|
||||
navigatorKey: APIs.navigatorKey,
|
||||
initialLocation: WelcomePage.routeTv,
|
||||
routes: [
|
||||
shellRoute,
|
||||
GoRoute(
|
||||
@@ -153,7 +156,7 @@ class _MyAppState extends ConsumerState<MyApp> {
|
||||
}
|
||||
|
||||
class MainSkeleton extends StatefulWidget {
|
||||
final Widget body;
|
||||
final StatefulNavigationShell body;
|
||||
const MainSkeleton({super.key, required this.body});
|
||||
|
||||
@override
|
||||
@@ -163,23 +166,9 @@ class MainSkeleton extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MainSkeletonState extends State<MainSkeleton> {
|
||||
var _selectedTab;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var uri = GoRouterState.of(context).uri.toString();
|
||||
if (uri.contains(WelcomePage.routeTv)) {
|
||||
_selectedTab = 0;
|
||||
} else if (uri.contains(WelcomePage.routeMoivie)) {
|
||||
_selectedTab = 1;
|
||||
} else if (uri.contains(ActivityPage.route)) {
|
||||
_selectedTab = 2;
|
||||
} else if (uri.contains(SystemSettingsPage.route)) {
|
||||
_selectedTab = 3;
|
||||
} else if (uri.contains(SystemPage.route)) {
|
||||
_selectedTab = 4;
|
||||
}
|
||||
|
||||
var padding = isSmallScreen(context) ? 5.0 : 20.0;
|
||||
return AdaptiveScaffold(
|
||||
appBarBreakpoint: Breakpoints.standard,
|
||||
appBar: AppBar(
|
||||
@@ -189,24 +178,28 @@ class _MainSkeletonState extends State<MainSkeleton> {
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: TextButton(
|
||||
onPressed: () => context.go(WelcomePage.routeTv),
|
||||
child: const Text(
|
||||
"Polaris",
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontSize: 28),
|
||||
leading: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextButton(
|
||||
onPressed: () => context.go(WelcomePage.routeTv),
|
||||
child: const Text(
|
||||
"Polaris",
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontSize: 28),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
actions: [
|
||||
SearchAnchor(
|
||||
leadingWidth: isSmallScreen(context) ? 0 : 190,
|
||||
title: Container(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: SearchAnchor(
|
||||
builder: (BuildContext context, SearchController controller) {
|
||||
return Container(
|
||||
constraints: const BoxConstraints(maxWidth: 250, maxHeight: 40),
|
||||
child: Opacity(
|
||||
opacity: 0.8,
|
||||
child: SearchBar(
|
||||
hintText: "搜索...",
|
||||
hintText: "在此搜索...",
|
||||
leading: const Icon(Icons.search),
|
||||
controller: controller,
|
||||
shadowColor: WidgetStateColor.transparent,
|
||||
@@ -222,9 +215,19 @@ class _MainSkeletonState extends State<MainSkeleton> {
|
||||
(BuildContext context, SearchController controller) {
|
||||
return [Text("dadada")];
|
||||
}),
|
||||
),
|
||||
|
||||
actions: [
|
||||
// IconButton(
|
||||
// onPressed: () => showCalendar(context),
|
||||
// icon: Icon(Icons.calendar_month)),
|
||||
IconButton(
|
||||
onPressed: () => showCalendar(context),
|
||||
icon: Icon(Icons.calendar_month)),
|
||||
onPressed: () => showDonate(context),
|
||||
icon: Icon(
|
||||
Icons.favorite_rounded,
|
||||
color: Colors.red,
|
||||
)),
|
||||
|
||||
MenuAnchor(
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
@@ -247,27 +250,14 @@ class _MainSkeletonState extends State<MainSkeleton> {
|
||||
child: const Icon(Icons.account_circle),
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
useDrawer: false,
|
||||
selectedIndex: _selectedTab,
|
||||
onSelectedIndexChange: (int index) {
|
||||
setState(() {
|
||||
_selectedTab = index;
|
||||
});
|
||||
if (index == 0) {
|
||||
context.go(WelcomePage.routeTv);
|
||||
} else if (index == 1) {
|
||||
context.go(WelcomePage.routeMoivie);
|
||||
} else if (index == 2) {
|
||||
context.go(ActivityPage.route);
|
||||
} else if (index == 3) {
|
||||
context.go(SystemSettingsPage.route);
|
||||
} else if (index == 4) {
|
||||
context.go(SystemPage.route);
|
||||
}
|
||||
},
|
||||
selectedIndex: widget.body.currentIndex,
|
||||
onSelectedIndexChange: (p0) => widget.body
|
||||
.goBranch(p0, initialLocation: p0 == widget.body.currentIndex),
|
||||
|
||||
destinations: const <NavigationDestination>[
|
||||
NavigationDestination(
|
||||
icon: Icon(Icons.live_tv_outlined),
|
||||
@@ -295,11 +285,33 @@ class _MainSkeletonState extends State<MainSkeleton> {
|
||||
label: '系统',
|
||||
),
|
||||
],
|
||||
body: (context) => SafeArea(child: widget.body),
|
||||
body: (context) => SafeArea(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: padding, right: padding, top: 5, bottom: 5),
|
||||
child: widget.body)),
|
||||
// Define a default secondaryBody.
|
||||
// Override the default secondaryBody during the smallBreakpoint to be
|
||||
// empty. Must use AdaptiveScaffold.emptyBuilder to ensure it is properly
|
||||
// overridden.
|
||||
);
|
||||
}
|
||||
|
||||
showDonate(BuildContext context) {
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("项目开发不易,给开发者加个鸡腿:"),
|
||||
content: SizedBox(
|
||||
width: 350,
|
||||
height: 400,
|
||||
child: Ink.image(
|
||||
fit: BoxFit.fitWidth,
|
||||
image: AssetImage("assets/wechat.jpg"))),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,21 +28,22 @@ class _MovieDetailsPageState extends ConsumerState<MovieDetailsPage> {
|
||||
Widget build(BuildContext context) {
|
||||
var seriesDetails = ref.watch(mediaDetailsProvider(widget.id));
|
||||
|
||||
return seriesDetails.when(
|
||||
data: (details) {
|
||||
return ListView(
|
||||
children: [
|
||||
DetailCard(details: details),
|
||||
NestedTabBar(
|
||||
id: widget.id,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, trace) {
|
||||
return Text("$err");
|
||||
},
|
||||
loading: () => const MyProgressIndicator());
|
||||
return SelectionArea(
|
||||
child: seriesDetails.when(
|
||||
data: (details) {
|
||||
return ListView(
|
||||
children: [
|
||||
DetailCard(details: details),
|
||||
NestedTabBar(
|
||||
id: widget.id,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, trace) {
|
||||
return Text("$err");
|
||||
},
|
||||
loading: () => const MyProgressIndicator()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -8,9 +10,12 @@ class APIs {
|
||||
static final _baseUrl = baseUrl();
|
||||
static final searchUrl = "$_baseUrl/api/v1/media/search";
|
||||
static final editMediaUrl = "$_baseUrl/api/v1/media/edit";
|
||||
static final downloadAllUrl = "$_baseUrl/api/v1/media/downloadall/";
|
||||
static final downloadAllEpisodesUrl = "$_baseUrl/api/v1/media/downloadall/";
|
||||
static final downloadAllTvUrl = "$_baseUrl/api/v1/media/download/tv";
|
||||
static final downloadAllMovieUrl = "$_baseUrl/api/v1/media/download/movie";
|
||||
static final settingsUrl = "$_baseUrl/api/v1/setting/do";
|
||||
static final settingsGeneralUrl = "$_baseUrl/api/v1/setting/general";
|
||||
//static final singleSettingUrl = "$_baseUrl/api/v1/setting/";
|
||||
static final watchlistTvUrl = "$_baseUrl/api/v1/media/tv/watchlist";
|
||||
static final watchlistMovieUrl = "$_baseUrl/api/v1/media/movie/watchlist";
|
||||
static final availableTorrentsUrl = "$_baseUrl/api/v1/media/torrents/";
|
||||
@@ -40,6 +45,7 @@ class APIs {
|
||||
static final addImportlistUrl = "$_baseUrl/api/v1/importlist/add";
|
||||
static final deleteImportlistUrl = "$_baseUrl/api/v1/importlist/delete";
|
||||
static final getAllImportlists = "$_baseUrl/api/v1/importlist/";
|
||||
static final prowlarrUrl = "$_baseUrl/api/v1/setting/prowlarr";
|
||||
|
||||
static final notifierAllUrl = "$_baseUrl/api/v1/notifier/all";
|
||||
static final notifierDeleteUrl = "$_baseUrl/api/v1/notifier/id/";
|
||||
@@ -49,6 +55,9 @@ class APIs {
|
||||
|
||||
static final cronJobUrl = "$_baseUrl/api/v1/setting/cron/trigger";
|
||||
|
||||
static final tvParseUrl = "$_baseUrl/api/v1/setting/parse/tv";
|
||||
static final movieParseUrl = "$_baseUrl/api/v1/setting/parse/movie";
|
||||
|
||||
static const tmdbApiKey = "tmdb_api_key";
|
||||
static const downloadDirKey = "download_dir";
|
||||
|
||||
@@ -113,4 +122,50 @@ class APIs {
|
||||
throw sp.message;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<String>> downloadAllTv() async {
|
||||
var resp = await getDio().get(APIs.downloadAllTvUrl);
|
||||
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
return sp.data==null? []:sp.data as List<String>;
|
||||
}
|
||||
|
||||
static Future<List<String>> downloadAllMovies() async {
|
||||
var resp = await getDio().get(APIs.downloadAllMovieUrl);
|
||||
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
return sp.data==null? []:sp.data as List<String>;
|
||||
}
|
||||
|
||||
static Future<String> parseTvName(String s) async {
|
||||
var resp = await getDio().post(APIs.tvParseUrl, data: {"s": s});
|
||||
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
JsonEncoder encoder = new JsonEncoder.withIndent(' ');
|
||||
return encoder.convert(sp.data);
|
||||
}
|
||||
|
||||
static Future<String> parseMovieName(String s) async {
|
||||
var resp = await getDio().post(APIs.movieParseUrl, data: {"s": s});
|
||||
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
JsonEncoder encoder = new JsonEncoder.withIndent(' ');
|
||||
return encoder.convert(sp.data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class SeriesDetailData
|
||||
|
||||
Future<dynamic> downloadall() async {
|
||||
final dio = APIs.getDio();
|
||||
var resp = await dio.get(APIs.downloadAllUrl + id!);
|
||||
var resp = await dio.get(APIs.downloadAllEpisodesUrl + id!);
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
|
||||
@@ -25,10 +25,14 @@ var importlistProvider =
|
||||
AsyncNotifierProvider.autoDispose<ImportListData, List<ImportList>>(
|
||||
ImportListData.new);
|
||||
|
||||
var prowlarrSettingDataProvider =
|
||||
AsyncNotifierProvider.autoDispose<ProwlarrSettingData, ProwlarrSetting>(
|
||||
ProwlarrSettingData.new);
|
||||
|
||||
class EditSettingData extends AutoDisposeAsyncNotifier<GeneralSetting> {
|
||||
@override
|
||||
FutureOr<GeneralSetting> build() async {
|
||||
final dio = await APIs.getDio();
|
||||
final dio = APIs.getDio();
|
||||
|
||||
var resp = await dio.get(APIs.settingsGeneralUrl);
|
||||
var rrr = ServerResponse.fromJson(resp.data);
|
||||
@@ -503,3 +507,44 @@ class ImportListData extends AutoDisposeAsyncNotifier<List<ImportList>> {
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
class ProwlarrSetting {
|
||||
final bool disabled;
|
||||
final String apiKey;
|
||||
final String url;
|
||||
ProwlarrSetting(
|
||||
{required this.apiKey, required this.url, required this.disabled});
|
||||
factory ProwlarrSetting.fromJson(Map<String, dynamic> json) {
|
||||
return ProwlarrSetting(
|
||||
apiKey: json["api_key"],
|
||||
url: json["url"],
|
||||
disabled: json["disabled"] ?? false);
|
||||
}
|
||||
|
||||
Map<String, dynamic> tojson() =>
|
||||
{"api_key": apiKey, "url": url, "disabled": disabled};
|
||||
}
|
||||
|
||||
class ProwlarrSettingData extends AutoDisposeAsyncNotifier<ProwlarrSetting> {
|
||||
@override
|
||||
FutureOr<ProwlarrSetting> build() async {
|
||||
final dio = APIs.getDio();
|
||||
var resp = await dio.get(APIs.prowlarrUrl);
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
var se = ProwlarrSetting.fromJson(sp.data);
|
||||
return se;
|
||||
}
|
||||
|
||||
Future<void> save(ProwlarrSetting ps) async {
|
||||
final dio = APIs.getDio();
|
||||
var resp = await dio.post(APIs.prowlarrUrl, data: ps.tojson());
|
||||
var sp = ServerResponse.fromJson(resp.data);
|
||||
if (sp.code != 0) {
|
||||
throw sp.message;
|
||||
}
|
||||
ref.invalidateSelf();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:ui/providers/APIs.dart';
|
||||
import 'package:ui/providers/welcome_data.dart';
|
||||
import 'package:ui/search_page/submit_dialog.dart';
|
||||
import 'package:ui/widgets/progress_indicator.dart';
|
||||
import 'package:ui/widgets/widgets.dart';
|
||||
|
||||
class SearchPage extends ConsumerStatefulWidget {
|
||||
const SearchPage({super.key, this.query});
|
||||
@@ -110,7 +111,7 @@ class _SearchPageState extends ConsumerState<SearchPage> {
|
||||
}
|
||||
return cards;
|
||||
},
|
||||
error: (err, trace) => [Text("$err")],
|
||||
error: (err, trace) => [PoError(msg: "网络错误,请确认TMDB Key正确配置,并且服务端能够正常连接TMDB网站", err: err)],
|
||||
loading: () => [const MyProgressIndicator()]);
|
||||
|
||||
var f = NotificationListener(
|
||||
|
||||
@@ -94,7 +94,7 @@ class _AuthState extends ConsumerState<AuthSettings> {
|
||||
],
|
||||
));
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class _DownloaderState extends ConsumerState<DownloaderSettings> {
|
||||
onTap: () => showDownloadClientDetails(DownloadClient()),
|
||||
child: const Icon(Icons.add));
|
||||
})),
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class _ImportlistState extends ConsumerState<Importlist> {
|
||||
child: const Icon(Icons.add));
|
||||
}),
|
||||
),
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class _IndexerState extends ConsumerState<IndexerSettings> {
|
||||
child: const Icon(Icons.add));
|
||||
}),
|
||||
),
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class _NotifierState extends ConsumerState<NotifierSettings> {
|
||||
child: const Icon(Icons.add));
|
||||
}),
|
||||
),
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
|
||||
|
||||
85
ui/lib/settings/prowlarr.dart
Normal file
85
ui/lib/settings/prowlarr.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:ui/providers/settings.dart';
|
||||
import 'package:ui/widgets/progress_indicator.dart';
|
||||
import 'package:ui/widgets/utils.dart';
|
||||
import 'package:ui/widgets/widgets.dart';
|
||||
|
||||
class ProwlarrSettingPage extends ConsumerStatefulWidget {
|
||||
const ProwlarrSettingPage({super.key});
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() {
|
||||
return ProwlarrSettingState();
|
||||
}
|
||||
}
|
||||
|
||||
class ProwlarrSettingState extends ConsumerState<ProwlarrSettingPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var ps = ref.watch(prowlarrSettingDataProvider);
|
||||
return ps.when(
|
||||
data: (v) => FormBuilder(
|
||||
key: _formKey, //设置globalKey,用于后面获取FormState
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
initialValue: {
|
||||
"api_key": v.apiKey,
|
||||
"url": v.url,
|
||||
"disabled": v.disabled
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
name: "url",
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Prowlarr URL",
|
||||
icon: Icon(Icons.web),
|
||||
hintText: "http://10.0.0.8:9696"),
|
||||
validator: FormBuilderValidators.required(),
|
||||
),
|
||||
FormBuilderTextField(
|
||||
name: "api_key",
|
||||
decoration: const InputDecoration(
|
||||
labelText: "API Key",
|
||||
icon: Icon(Icons.key),
|
||||
helperText: "Prowlarr 设置 -> 通用 -> API 密钥"),
|
||||
validator: FormBuilderValidators.required(),
|
||||
),
|
||||
FormBuilderSwitch(
|
||||
name: "disabled",
|
||||
title: const Text("禁用 Prowlarr"),
|
||||
decoration: InputDecoration(icon: Icon(Icons.do_not_disturb)),
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState!.saveAndValidate()) {
|
||||
var values = _formKey.currentState!.value;
|
||||
var f = ref
|
||||
.read(prowlarrSettingDataProvider.notifier)
|
||||
.save(ProwlarrSetting(
|
||||
apiKey: values["api_key"],
|
||||
url: values["url"],
|
||||
disabled: values["disabled"]))
|
||||
.then((v) => showSnakeBar("更新成功"));
|
||||
showLoadingWithFuture(f);
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: Text("保存"),
|
||||
)),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:ui/settings/general.dart';
|
||||
import 'package:ui/settings/importlist.dart';
|
||||
import 'package:ui/settings/indexer.dart';
|
||||
import 'package:ui/settings/notifier.dart';
|
||||
import 'package:ui/settings/prowlarr.dart';
|
||||
import 'package:ui/settings/storage.dart';
|
||||
|
||||
class SystemSettingsPage extends ConsumerStatefulWidget {
|
||||
@@ -21,17 +22,19 @@ class SystemSettingsPage extends ConsumerStatefulWidget {
|
||||
class _SystemSettingsPageState extends ConsumerState<SystemSettingsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView(
|
||||
return SelectionArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
getExpansionTile("常规", const GeneralSettings()),
|
||||
getExpansionTile("索引器", const IndexerSettings()),
|
||||
getExpansionTile("Prowlarr 设置", const ProwlarrSettingPage()),
|
||||
getExpansionTile("下载器", const DownloaderSettings()),
|
||||
getExpansionTile("存储", const StorageSettings()),
|
||||
getExpansionTile("通知客户端", const NotifierSettings()),
|
||||
getExpansionTile("监控列表", const Importlist()),
|
||||
getExpansionTile("认证", const AuthSettings())
|
||||
],
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
Widget getExpansionTile(String name, Widget body) {
|
||||
|
||||
@@ -35,7 +35,7 @@ class _StorageState extends ConsumerState<StorageSettings> {
|
||||
child: const Icon(Icons.add));
|
||||
}),
|
||||
),
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ class _SystemPageState extends ConsumerState<SystemPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final logs = ref.watch(logFileDataProvider);
|
||||
final about = ref.watch(aboutDataProvider);
|
||||
return SingleChildScrollView(
|
||||
return SelectionArea(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
ExpansionTile(
|
||||
@@ -55,7 +56,7 @@ class _SystemPageState extends ConsumerState<SystemPage> {
|
||||
]);
|
||||
}));
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator())
|
||||
],
|
||||
),
|
||||
@@ -110,7 +111,7 @@ class _SystemPageState extends ConsumerState<SystemPage> {
|
||||
]),
|
||||
]);
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator())
|
||||
],
|
||||
),
|
||||
@@ -184,12 +185,12 @@ class _SystemPageState extends ConsumerState<SystemPage> {
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, trace) => Text("$err"),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator())
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,156 +34,157 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
var seriesDetails = ref.watch(mediaDetailsProvider(widget.seriesId));
|
||||
return seriesDetails.when(
|
||||
data: (details) {
|
||||
Map<int, List<DataRow>> m = {};
|
||||
for (final ep in details.episodes!) {
|
||||
var row = DataRow(cells: [
|
||||
DataCell(Text("${ep.episodeNumber}")),
|
||||
DataCell(Text("${ep.title}")),
|
||||
DataCell(Opacity(
|
||||
opacity: 0.5,
|
||||
child: Text(ep.airDate ?? "-"),
|
||||
)),
|
||||
DataCell(
|
||||
Opacity(
|
||||
opacity: 0.7,
|
||||
child: ep.status == "downloading"
|
||||
? const IconButton(
|
||||
tooltip: "下载中",
|
||||
onPressed: null,
|
||||
icon: Icon(Icons.downloading))
|
||||
: (ep.status == "downloaded"
|
||||
return SelectionArea(
|
||||
child: seriesDetails.when(
|
||||
data: (details) {
|
||||
Map<int, List<DataRow>> m = {};
|
||||
for (final ep in details.episodes!) {
|
||||
var row = DataRow(cells: [
|
||||
DataCell(Text("${ep.episodeNumber}")),
|
||||
DataCell(Text("${ep.title}")),
|
||||
DataCell(Opacity(
|
||||
opacity: 0.5,
|
||||
child: Text(ep.airDate ?? "-"),
|
||||
)),
|
||||
DataCell(
|
||||
Opacity(
|
||||
opacity: 0.7,
|
||||
child: ep.status == "downloading"
|
||||
? const IconButton(
|
||||
tooltip: "已下载",
|
||||
tooltip: "下载中",
|
||||
onPressed: null,
|
||||
icon: Icon(Icons.download_done))
|
||||
: (ep.monitored == true
|
||||
? IconButton(
|
||||
tooltip: "监控中",
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(mediaDetailsProvider(
|
||||
widget.seriesId)
|
||||
.notifier)
|
||||
.changeMonitoringStatus(
|
||||
ep.id!, false);
|
||||
},
|
||||
icon: const Icon(Icons.alarm))
|
||||
: Opacity(
|
||||
opacity: 0.7,
|
||||
child: IconButton(
|
||||
tooltip: "未监控",
|
||||
icon: Icon(Icons.downloading))
|
||||
: (ep.status == "downloaded"
|
||||
? const IconButton(
|
||||
tooltip: "已下载",
|
||||
onPressed: null,
|
||||
icon: Icon(Icons.download_done))
|
||||
: (ep.monitored == true
|
||||
? IconButton(
|
||||
tooltip: "监控中",
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(mediaDetailsProvider(
|
||||
widget.seriesId)
|
||||
.notifier)
|
||||
.changeMonitoringStatus(
|
||||
ep.id!, true);
|
||||
ep.id!, false);
|
||||
},
|
||||
icon: const Icon(Icons.alarm_off)),
|
||||
)))),
|
||||
),
|
||||
DataCell(Row(
|
||||
children: [
|
||||
LoadingIconButton(
|
||||
tooltip: "搜索下载对应剧集",
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(
|
||||
mediaDetailsProvider(widget.seriesId).notifier)
|
||||
.searchAndDownload(widget.seriesId,
|
||||
ep.seasonNumber!, ep.episodeNumber!)
|
||||
.then((v) => showSnakeBar("开始下载: $v"));
|
||||
},
|
||||
icon: Icons.download),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
icon: const Icon(Icons.alarm))
|
||||
: Opacity(
|
||||
opacity: 0.7,
|
||||
child: IconButton(
|
||||
tooltip: "未监控",
|
||||
onPressed: () {
|
||||
ref
|
||||
.read(mediaDetailsProvider(
|
||||
widget.seriesId)
|
||||
.notifier)
|
||||
.changeMonitoringStatus(
|
||||
ep.id!, true);
|
||||
},
|
||||
icon: const Icon(Icons.alarm_off)),
|
||||
)))),
|
||||
),
|
||||
Tooltip(
|
||||
message: "查看可用资源",
|
||||
child: IconButton(
|
||||
onPressed: () => showAvailableTorrents(widget.seriesId,
|
||||
ep.seasonNumber ?? 0, ep.episodeNumber ?? 0),
|
||||
icon: const Icon(Icons.manage_search)),
|
||||
)
|
||||
],
|
||||
))
|
||||
]);
|
||||
|
||||
if (m[ep.seasonNumber] == null) {
|
||||
m[ep.seasonNumber!] = List.empty(growable: true);
|
||||
}
|
||||
m[ep.seasonNumber!]!.add(row);
|
||||
}
|
||||
List<ExpansionTile> list = List.empty(growable: true);
|
||||
for (final k in m.keys.toList().reversed) {
|
||||
final seasonEpisodes = DataTable(columns: [
|
||||
const DataColumn(label: Text("#")),
|
||||
const DataColumn(
|
||||
label: Text("标题"),
|
||||
),
|
||||
const DataColumn(label: Text("播出时间")),
|
||||
const DataColumn(label: Text("状态")),
|
||||
DataColumn(
|
||||
label: Row(
|
||||
children: [
|
||||
LoadingIconButton(
|
||||
tooltip: "搜索下载全部剧集",
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(
|
||||
mediaDetailsProvider(widget.seriesId).notifier)
|
||||
.searchAndDownload(widget.seriesId, k, 0)
|
||||
.then((v) => showSnakeBar("开始下载: $v"));
|
||||
//showLoadingWithFuture(f);
|
||||
},
|
||||
icon: Icons.download),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Tooltip(
|
||||
message: "查看可用资源",
|
||||
child: IconButton(
|
||||
onPressed: () =>
|
||||
showAvailableTorrents(widget.seriesId, k, 0),
|
||||
icon: const Icon(Icons.manage_search)),
|
||||
)
|
||||
],
|
||||
))
|
||||
], rows: m[k]!);
|
||||
|
||||
var seasonList = ExpansionTile(
|
||||
tilePadding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||
//childrenPadding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
|
||||
initiallyExpanded: false,
|
||||
title: k == 0 ? const Text("特别篇") : Text("第 $k 季"),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
screenWidth < 600
|
||||
? SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: seasonEpisodes,
|
||||
DataCell(Row(
|
||||
children: [
|
||||
LoadingIconButton(
|
||||
tooltip: "搜索下载对应剧集",
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(mediaDetailsProvider(widget.seriesId)
|
||||
.notifier)
|
||||
.searchAndDownload(widget.seriesId,
|
||||
ep.seasonNumber!, ep.episodeNumber!)
|
||||
.then((v) => showSnakeBar("开始下载: $v"));
|
||||
},
|
||||
icon: Icons.download),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Tooltip(
|
||||
message: "查看可用资源",
|
||||
child: IconButton(
|
||||
onPressed: () => showAvailableTorrents(
|
||||
widget.seriesId,
|
||||
ep.seasonNumber ?? 0,
|
||||
ep.episodeNumber ?? 0),
|
||||
icon: const Icon(Icons.manage_search)),
|
||||
)
|
||||
: seasonEpisodes
|
||||
],
|
||||
);
|
||||
list.add(seasonList);
|
||||
}
|
||||
return ListView(
|
||||
children: [
|
||||
DetailCard(details: details),
|
||||
Column(
|
||||
children: list,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, trace) {
|
||||
return Text("$err");
|
||||
},
|
||||
loading: () => const MyProgressIndicator());
|
||||
],
|
||||
))
|
||||
]);
|
||||
|
||||
if (m[ep.seasonNumber] == null) {
|
||||
m[ep.seasonNumber!] = List.empty(growable: true);
|
||||
}
|
||||
m[ep.seasonNumber!]!.add(row);
|
||||
}
|
||||
List<ExpansionTile> list = List.empty(growable: true);
|
||||
for (final k in m.keys.toList().reversed) {
|
||||
final seasonEpisodes = DataTable(columns: [
|
||||
const DataColumn(label: Text("#")),
|
||||
const DataColumn(
|
||||
label: Text("标题"),
|
||||
),
|
||||
const DataColumn(label: Text("播出时间")),
|
||||
const DataColumn(label: Text("状态")),
|
||||
DataColumn(
|
||||
label: Row(
|
||||
children: [
|
||||
LoadingIconButton(
|
||||
tooltip: "搜索下载全部剧集",
|
||||
onPressed: () async {
|
||||
await ref
|
||||
.read(mediaDetailsProvider(widget.seriesId)
|
||||
.notifier)
|
||||
.searchAndDownload(widget.seriesId, k, 0)
|
||||
.then((v) => showSnakeBar("开始下载: $v"));
|
||||
//showLoadingWithFuture(f);
|
||||
},
|
||||
icon: Icons.download),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Tooltip(
|
||||
message: "查看可用资源",
|
||||
child: IconButton(
|
||||
onPressed: () =>
|
||||
showAvailableTorrents(widget.seriesId, k, 0),
|
||||
icon: const Icon(Icons.manage_search)),
|
||||
)
|
||||
],
|
||||
))
|
||||
], rows: m[k]!);
|
||||
|
||||
var seasonList = ExpansionTile(
|
||||
tilePadding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
|
||||
//childrenPadding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
|
||||
initiallyExpanded: false,
|
||||
title: k == 0 ? const Text("特别篇") : Text("第 $k 季"),
|
||||
expandedCrossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
screenWidth < 600
|
||||
? SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: seasonEpisodes,
|
||||
)
|
||||
: seasonEpisodes
|
||||
],
|
||||
);
|
||||
list.add(seasonList);
|
||||
}
|
||||
return ListView(
|
||||
children: [
|
||||
DetailCard(details: details),
|
||||
Column(
|
||||
children: list,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator()));
|
||||
}
|
||||
|
||||
Future<void> showAvailableTorrents(String id, int season, int episode) {
|
||||
|
||||
@@ -1,59 +1,244 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:ui/movie_watchlist.dart';
|
||||
import 'package:ui/providers/APIs.dart';
|
||||
import 'package:ui/providers/welcome_data.dart';
|
||||
import 'package:ui/tv_details.dart';
|
||||
import 'package:ui/widgets/progress_indicator.dart';
|
||||
import 'package:ui/widgets/utils.dart';
|
||||
import 'package:ui/widgets/widgets.dart';
|
||||
|
||||
class WelcomePage extends ConsumerWidget {
|
||||
class WelcomePage extends ConsumerStatefulWidget {
|
||||
const WelcomePage({super.key});
|
||||
static const routeTv = "/series";
|
||||
static const routeMoivie = "/movies";
|
||||
|
||||
const WelcomePage({super.key});
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() {
|
||||
return WelcomePageState();
|
||||
}
|
||||
}
|
||||
|
||||
class WelcomePageState extends ConsumerState<WelcomePage> {
|
||||
//WelcomePageState({super.key});
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
bool onlyShowUnfinished = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
var uri = GoRouterState.of(context).uri.toString();
|
||||
|
||||
AsyncValue<List<MediaDetail>> data;
|
||||
if (uri == routeMoivie) {
|
||||
if (uri == WelcomePage.routeMoivie) {
|
||||
data = ref.watch(movieWatchlistDataProvider);
|
||||
} else {
|
||||
data = ref.watch(tvWatchlistDataProvider);
|
||||
}
|
||||
|
||||
return switch (data) {
|
||||
AsyncData(:final value) => SingleChildScrollView(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
spacing: isSmallScreen(context) ? 0 : 10,
|
||||
runSpacing: isSmallScreen(context) ? 10 : 20,
|
||||
children: value.isEmpty
|
||||
? [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
"啥都没有...",
|
||||
style: TextStyle(fontSize: 16),
|
||||
))
|
||||
]
|
||||
: List.generate(value.length, (i) {
|
||||
final item = value[i];
|
||||
return MediaCard(item: item);
|
||||
}),
|
||||
),
|
||||
),
|
||||
_ => const MyProgressIndicator(),
|
||||
};
|
||||
return SelectionArea(
|
||||
child: Stack(
|
||||
//alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
() {
|
||||
return data.when(
|
||||
data: (value) => SingleChildScrollView(
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
spacing: isSmallScreen(context) ? 0 : 10,
|
||||
runSpacing: isSmallScreen(context) ? 10 : 20,
|
||||
children: getMediaAll(value),
|
||||
),
|
||||
),
|
||||
error: (err, trace) => PoNetworkError(err: err),
|
||||
loading: () => const MyProgressIndicator());
|
||||
}(),
|
||||
getMoreButtonAndActions(uri)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getMoreButtonAndActions(String uri) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(child: Container()),
|
||||
Column(
|
||||
children: [
|
||||
Expanded(child: Container()),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
child: MenuAnchor(
|
||||
style: MenuStyle(
|
||||
alignment: Alignment.topLeft,
|
||||
backgroundColor: WidgetStatePropertyAll(Theme.of(context)
|
||||
.colorScheme
|
||||
.inversePrimary
|
||||
.withOpacity(0.7)),
|
||||
),
|
||||
menuChildren: [parseName(), onlyUnfinished(), refreshAll(uri)],
|
||||
builder: (context, controller, child) {
|
||||
return Opacity(
|
||||
opacity: 0.7,
|
||||
child: FloatingActionButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
child: const Icon(Icons.more_horiz),
|
||||
));
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget onlyUnfinished() {
|
||||
return CheckboxListTile(
|
||||
value: onlyShowUnfinished,
|
||||
onChanged: (b) {
|
||||
setState(() {
|
||||
onlyShowUnfinished = b!;
|
||||
});
|
||||
},
|
||||
title: const Text(
|
||||
"未完成",
|
||||
style: TextStyle(fontSize: 16),
|
||||
softWrap: false,
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
);
|
||||
}
|
||||
|
||||
Widget refreshAll(String uri) {
|
||||
return LoadingListTile(
|
||||
icon: Icons.refresh,
|
||||
text: "全部更新",
|
||||
onPressed: () async {
|
||||
if (uri == WelcomePage.routeMoivie) {
|
||||
await APIs.downloadAllMovies().then((v) {
|
||||
if (v.isNotEmpty) {
|
||||
showSnakeBar("开始下载电影:$v");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await APIs.downloadAllTv().then((v) {
|
||||
if (v.isNotEmpty) {
|
||||
showSnakeBar("开始下载剧集:$v");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget parseName() {
|
||||
return ListTile(
|
||||
leading: Icon(Icons.calculate),
|
||||
title: Text("测试解析"),
|
||||
onTap: () => _showNameParsingDialog(),
|
||||
);
|
||||
}
|
||||
|
||||
bool isSmallScreen(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
return screenWidth < 600;
|
||||
}
|
||||
|
||||
List<Widget> getMediaAll(List<MediaDetail> list) {
|
||||
if (list.isEmpty) {
|
||||
return [
|
||||
Container(
|
||||
height: MediaQuery.of(context).size.height * 0.6,
|
||||
alignment: Alignment.center,
|
||||
child: const Text(
|
||||
"啥都没有...",
|
||||
style: TextStyle(fontSize: 16),
|
||||
))
|
||||
];
|
||||
}
|
||||
if (onlyShowUnfinished) {
|
||||
list = list.where((v) => v.downloadedNum != v.monitoredNum).toList();
|
||||
}
|
||||
return List.generate(list.length, (i) {
|
||||
final item = list[i];
|
||||
return MediaCard(item: item);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showNameParsingDialog() async {
|
||||
final resultController = TextEditingController();
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('测试名称解析'),
|
||||
content: SizedBox(
|
||||
width: 500,
|
||||
height: 400,
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
initialValue: {"name": "", "type": "tv"},
|
||||
child: Column(
|
||||
children: [
|
||||
FormBuilderTextField(
|
||||
name: "name",
|
||||
decoration: InputDecoration(labelText: "要解析的名字"),
|
||||
validator: FormBuilderValidators.required(),
|
||||
),
|
||||
FormBuilderDropdown(
|
||||
name: "type",
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
value: "tv",
|
||||
child: const Text("电视剧"),
|
||||
),
|
||||
DropdownMenuItem(value: "movie", child: const Text("电影"))
|
||||
],
|
||||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10),
|
||||
child: LoadingTextButton(
|
||||
onPressed: () async {
|
||||
if (_formKey.currentState!.saveAndValidate()) {
|
||||
final values = _formKey.currentState!.value;
|
||||
//print(values);
|
||||
if (values["type"] == "tv") {
|
||||
var s = await APIs.parseTvName(values["name"]);
|
||||
resultController.text = s;
|
||||
} else {
|
||||
var s =
|
||||
await APIs.parseMovieName(values["name"]);
|
||||
resultController.text = s;
|
||||
}
|
||||
}
|
||||
return;
|
||||
},
|
||||
label: Text("解析")),
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
maxLines: 8,
|
||||
controller: resultController,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MediaCard extends StatelessWidget {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:ui/providers/APIs.dart';
|
||||
import 'dart:io' show Platform;
|
||||
@@ -60,5 +61,5 @@ bool isDesktop() {
|
||||
|
||||
bool isSmallScreen(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
return screenWidth < 600;
|
||||
return screenWidth < Breakpoints.small.endWidth!.toDouble();
|
||||
}
|
||||
|
||||
@@ -141,8 +141,58 @@ class _MySliderState extends State<MyRangeSlider> {
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingListTile extends StatefulWidget {
|
||||
const LoadingListTile(
|
||||
{super.key,
|
||||
required this.onPressed,
|
||||
required this.icon,
|
||||
required this.text});
|
||||
final Future<void> Function() onPressed;
|
||||
final IconData icon;
|
||||
final String text;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return LoadingListTileState();
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingListTileState extends State<LoadingListTile> {
|
||||
bool loading = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
onTap: loading
|
||||
? null
|
||||
: () async {
|
||||
setState(() => loading = true);
|
||||
try {
|
||||
await widget.onPressed();
|
||||
} catch (e) {
|
||||
showSnakeBar("操作失败:$e");
|
||||
} finally {
|
||||
setState(() => loading = false);
|
||||
}
|
||||
},
|
||||
title: Text(widget.text),
|
||||
leading: loading
|
||||
? Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: const CircularProgressIndicator(
|
||||
color: Colors.grey,
|
||||
strokeWidth: 3,
|
||||
),
|
||||
)
|
||||
: Icon(widget.icon));
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingIconButton extends StatefulWidget {
|
||||
const LoadingIconButton({super.key, required this.onPressed, required this.icon, this.tooltip});
|
||||
const LoadingIconButton(
|
||||
{super.key, required this.onPressed, required this.icon, this.tooltip});
|
||||
final Future<void> Function() onPressed;
|
||||
final IconData icon;
|
||||
final String? tooltip;
|
||||
@@ -159,7 +209,7 @@ class _LoadingIconButtonState extends State<LoadingIconButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
tooltip: widget.tooltip,
|
||||
tooltip: widget.tooltip,
|
||||
onPressed: loading
|
||||
? null
|
||||
: () async {
|
||||
@@ -187,7 +237,8 @@ class _LoadingIconButtonState extends State<LoadingIconButton> {
|
||||
}
|
||||
|
||||
class LoadingTextButton extends StatefulWidget {
|
||||
const LoadingTextButton({super.key, required this.onPressed, required this.label});
|
||||
const LoadingTextButton(
|
||||
{super.key, required this.onPressed, required this.label});
|
||||
final Future<void> Function() onPressed;
|
||||
final Widget label;
|
||||
|
||||
@@ -230,3 +281,32 @@ class _LoadingTextButtonState extends State<LoadingTextButton> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PoError extends StatelessWidget {
|
||||
const PoError({super.key, required this.msg, required this.err});
|
||||
final String msg;
|
||||
final dynamic err;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text("$msg ", style: TextStyle(color:Theme.of(context).colorScheme.error),),
|
||||
Tooltip(
|
||||
message: "$err",
|
||||
child: Icon(Icons.info,color: Theme.of(context).colorScheme.error,),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PoNetworkError extends StatelessWidget {
|
||||
const PoNetworkError({super.key, required this.err});
|
||||
final dynamic err;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PoError(msg: "网络错误,请检查网络链接", err: err);
|
||||
}
|
||||
}
|
||||
|
||||
206
ui/pubspec.lock
206
ui/pubspec.lock
@@ -6,7 +6,7 @@ packages:
|
||||
description:
|
||||
name: another_flushbar
|
||||
sha256: "19bf9520230ec40b300aaf9dd2a8fefcb277b25ecd1c4838f530566965befc2a"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.30"
|
||||
another_transformer_page_view:
|
||||
@@ -14,7 +14,7 @@ packages:
|
||||
description:
|
||||
name: another_transformer_page_view
|
||||
sha256: a7cd46ede62d621c5abe7e58c7cb2745abe67b3bfec64f59b8889c93d7be7a8e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
async:
|
||||
@@ -22,7 +22,7 @@ packages:
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
@@ -30,7 +30,7 @@ packages:
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
characters:
|
||||
@@ -38,7 +38,7 @@ packages:
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
@@ -46,7 +46,7 @@ packages:
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
@@ -54,7 +54,7 @@ packages:
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
cupertino_icons:
|
||||
@@ -62,31 +62,31 @@ packages:
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0+1"
|
||||
version: "5.7.0"
|
||||
dio_web_adapter:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dio_web_adapter
|
||||
sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "2.0.0"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: equatable
|
||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
fake_async:
|
||||
@@ -94,7 +94,7 @@ packages:
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
flutter:
|
||||
@@ -106,26 +106,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_adaptive_scaffold
|
||||
sha256: "56d4d81fe88ecffe8ae96b8d89a1ae793c0a85035bb9b74ff28f20eea0cdbdc2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "8c515a2cb8abb3a567f8e77f10b33f47bb6fcadfe31f62364e0aca36280cdf93"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.11+1"
|
||||
version: "0.3.1"
|
||||
flutter_form_builder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_form_builder
|
||||
sha256: "447f8808f68070f7df968e8063aada3c9d2e90e789b5b70f3b44e4b315212656"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: c278ef69b08957d484f83413f0e77b656a39b7a7bb4eb8a295da3a820ecc6545
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "9.5.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "5.0.0"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -136,17 +136,17 @@ packages:
|
||||
description:
|
||||
name: flutter_login
|
||||
sha256: "1f7c46d0d76081cf4c5180e3a265b1f5b1d7e48c81859f58f03a8dcd27338b85"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_riverpod:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -162,7 +162,7 @@ packages:
|
||||
description:
|
||||
name: font_awesome_flutter
|
||||
sha256: "275ff26905134bcb59417cf60ad979136f1f8257f2f449914b2c3e05bbb4cd6f"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.7.0"
|
||||
form_builder_validators:
|
||||
@@ -170,23 +170,23 @@ packages:
|
||||
description:
|
||||
name: form_builder_validators
|
||||
sha256: c61ed7b1deecf0e1ebe49e2fa79e3283937c5a21c7e48e3ed9856a4a14e1191a
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.0"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.3"
|
||||
version: "14.3.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
@@ -194,7 +194,7 @@ packages:
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
intl_phone_number_input:
|
||||
@@ -202,7 +202,7 @@ packages:
|
||||
description:
|
||||
name: intl_phone_number_input
|
||||
sha256: "1c4328713a9503ab26a1fdbb6b00b4cada68c18aac922b35bedbc72eff1297c3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
js:
|
||||
@@ -210,7 +210,7 @@ packages:
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
leak_tracker:
|
||||
@@ -218,7 +218,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
@@ -226,7 +226,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
@@ -234,7 +234,7 @@ packages:
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
libphonenumber_platform_interface:
|
||||
@@ -242,7 +242,7 @@ packages:
|
||||
description:
|
||||
name: libphonenumber_platform_interface
|
||||
sha256: f801f6c65523f56504b83f0890e6dad584ab3a7507dca65fec0eed640afea40f
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
libphonenumber_plugin:
|
||||
@@ -250,7 +250,7 @@ packages:
|
||||
description:
|
||||
name: libphonenumber_plugin
|
||||
sha256: c615021d9816fbda2b2587881019ed595ecdf54d999652d7e4cce0e1f026368c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3"
|
||||
libphonenumber_web:
|
||||
@@ -258,31 +258,31 @@ packages:
|
||||
description:
|
||||
name: libphonenumber_web
|
||||
sha256: "8186f420dbe97c3132283e52819daff1e55d60d6db46f7ea5ac42f42a28cc2ef"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "5.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
@@ -290,7 +290,7 @@ packages:
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
@@ -298,7 +298,7 @@ packages:
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
nested:
|
||||
@@ -306,7 +306,7 @@ packages:
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
path:
|
||||
@@ -314,7 +314,7 @@ packages:
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
phone_numbers_parser:
|
||||
@@ -322,7 +322,7 @@ packages:
|
||||
description:
|
||||
name: phone_numbers_parser
|
||||
sha256: "62451b689d842791ed1fd5dc9eacf36ffa8bad23a78ad6cde732dc2fb222fae2"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
plugin_platform_interface:
|
||||
@@ -330,7 +330,7 @@ packages:
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
provider:
|
||||
@@ -338,31 +338,31 @@ packages:
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
quiver:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: quiver
|
||||
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.2.2"
|
||||
riverpod:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.1"
|
||||
version: "2.6.1"
|
||||
sign_in_button:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sign_in_button
|
||||
sha256: "977b9b0415d2f3909e642275dfabba7919ba8e111324641b76cae6d1acbd183e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
simple_gesture_detector:
|
||||
@@ -370,7 +370,7 @@ packages:
|
||||
description:
|
||||
name: simple_gesture_detector
|
||||
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
sky_engine:
|
||||
@@ -383,7 +383,7 @@ packages:
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
@@ -391,7 +391,7 @@ packages:
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
state_notifier:
|
||||
@@ -399,7 +399,7 @@ packages:
|
||||
description:
|
||||
name: state_notifier
|
||||
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
stream_channel:
|
||||
@@ -407,7 +407,7 @@ packages:
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
@@ -415,7 +415,7 @@ packages:
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
table_calendar:
|
||||
@@ -423,7 +423,7 @@ packages:
|
||||
description:
|
||||
name: table_calendar
|
||||
sha256: "4ca32b2fc919452c9974abd4c6ea611a63e33b9e4f0b8c38dba3ac1f4a6549d1"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
term_glyph:
|
||||
@@ -431,7 +431,7 @@ packages:
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
@@ -439,7 +439,7 @@ packages:
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
timeago:
|
||||
@@ -447,63 +447,63 @@ packages:
|
||||
description:
|
||||
name: timeago
|
||||
sha256: "054cedf68706bb142839ba0ae6b135f6b68039f0b8301cbe8784ae653d5ff8de"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.4.0"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.0"
|
||||
version: "6.3.1"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.8"
|
||||
version: "6.3.14"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "3.2.0"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
@@ -511,23 +511,23 @@ packages:
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.1.3"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
@@ -535,17 +535,17 @@ packages:
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.flutter-io.cn"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062
|
||||
url: "https://pub.flutter-io.cn"
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.4.3 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
||||
@@ -30,21 +30,21 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
dio: ^5.4.3+1
|
||||
dio: ^5.7.0
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
go_router: ^14.2.0
|
||||
flutter_riverpod: ^2.5.1
|
||||
quiver: ^3.2.1
|
||||
cupertino_icons: ^1.0.8
|
||||
go_router: ^14.3.0
|
||||
flutter_riverpod: ^2.6.1
|
||||
quiver: ^3.2.2
|
||||
flutter_login: ^5.0.0
|
||||
intl: ^0.19.0
|
||||
flutter_adaptive_scaffold: ^0.1.11+1
|
||||
flutter_form_builder: ^9.3.0
|
||||
flutter_adaptive_scaffold: ^0.3.1
|
||||
flutter_form_builder: ^9.5.0
|
||||
form_builder_validators: ^11.0.0
|
||||
url_launcher: ^6.3.0
|
||||
url_launcher: ^6.3.1
|
||||
timeago: ^3.7.0
|
||||
table_calendar: ^3.1.2
|
||||
|
||||
@@ -57,7 +57,7 @@ dev_dependencies:
|
||||
# activated in the `analysis_options.yaml` file located at the root of your
|
||||
# package. See that file for information about deactivating specific lint
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^4.0.0
|
||||
flutter_lints: ^5.0.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
@@ -71,7 +71,8 @@ flutter:
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
assets:
|
||||
- assets/
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
|
||||
Reference in New Issue
Block a user