├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── animeone
├── .gitignore
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── org
│ │ │ │ │ └── github
│ │ │ │ │ └── henryquan
│ │ │ │ │ └── animeone
│ │ │ │ │ ├── MainActivity.kt
│ │ │ │ │ └── WebActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable-v21
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── layout
│ │ │ │ └── activity_webview.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-ldpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ │ ├── values-night
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ ├── strings.xml
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon_20pt@1_20x20.png
│ │ │ ├── Icon_20pt@2_40x40-1.png
│ │ │ ├── Icon_20pt@2_40x40.png
│ │ │ ├── Icon_20pt@3_60x60.png
│ │ │ ├── Icon_29pt@1_29x29-1.png
│ │ │ ├── Icon_29pt@1_29x29.png
│ │ │ ├── Icon_29pt@2_58x58-1.png
│ │ │ ├── Icon_29pt@2_58x58.png
│ │ │ ├── Icon_29pt@3_87x87.png
│ │ │ ├── Icon_40pt@1_40x40.png
│ │ │ ├── Icon_40pt@2_80x80-1.png
│ │ │ ├── Icon_40pt@2_80x80.png
│ │ │ ├── Icon_40pt@3_120x120.png
│ │ │ ├── Icon_60pt@2_120x120.png
│ │ │ ├── Icon_60pt@3_180x180.png
│ │ │ ├── Icon_76pt@1_76x76.png
│ │ │ ├── Icon_76pt@2_152x152.png
│ │ │ ├── Icon_83.5pt@2_167x167.png
│ │ │ └── _AppIcon_1024x1024.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── Runner-Bridging-Header.h
├── lib
│ ├── WebsiteClosed.dart
│ ├── assets
│ │ ├── cover
│ │ │ ├── 3.jpg
│ │ │ ├── 4.jpg
│ │ │ ├── 5.jpg
│ │ │ ├── 6.jpg
│ │ │ ├── 7.jpg
│ │ │ ├── 8.jpg
│ │ │ ├── 9.jpg
│ │ │ └── README.md
│ │ └── icon
│ │ │ └── logo.png
│ ├── core
│ │ ├── AnimeOne.dart
│ │ ├── GlobalData.dart
│ │ ├── anime
│ │ │ ├── AnimeBasic.dart
│ │ │ ├── AnimeEntry.dart
│ │ │ ├── AnimeInfo.dart
│ │ │ ├── AnimeRecent.dart
│ │ │ ├── AnimeSchedule.dart
│ │ │ ├── AnimeSeason.dart
│ │ │ └── AnimeVideo.dart
│ │ ├── interface
│ │ │ └── FullscreenPlayer.dart
│ │ ├── other
│ │ │ └── GithubUpdate.dart
│ │ └── parser
│ │ │ ├── AnimeListParser.dart
│ │ │ ├── AnimeListParserV2.dart
│ │ │ ├── AnimePageParser.dart
│ │ │ ├── AnimeRecentParser.dart
│ │ │ ├── AnimeScheduleParser.dart
│ │ │ ├── BasicParser.dart
│ │ │ ├── GithubParser.dart
│ │ │ └── VideoSourceParser.dart
│ ├── main.dart
│ └── ui
│ │ ├── component
│ │ ├── AnimeButton.dart
│ │ ├── AnimeCoverImage.dart
│ │ ├── AnimeEntryCard.dart
│ │ ├── AnimeInfoCard.dart
│ │ ├── AnimeRecentTile.dart
│ │ ├── AnimeScheduleTile.dart
│ │ ├── EmailButton.dart
│ │ └── ErrorButton.dart
│ │ ├── page
│ │ ├── anime.dart
│ │ ├── home.dart
│ │ ├── latest.dart
│ │ ├── list.dart
│ │ ├── schedule.dart
│ │ ├── settings.dart
│ │ ├── support.dart
│ │ └── video.dart
│ │ └── widgets
│ │ └── flat_button.dart
├── pubspec.lock
├── pubspec.yaml
└── test
│ ├── .gitignore
│ ├── class_test.dart
│ ├── fetch_test.dart
│ └── widget_test.dart
├── design
├── class.md
├── html.md
├── logo
│ ├── Logo.png
│ ├── logo.xd
│ └── round.png
└── user_story.md
├── fastlane
└── metadata
│ └── android
│ ├── en-US
│ ├── full_description.txt
│ ├── images
│ │ └── phoneScreenshots
│ │ │ ├── 1.jpg
│ │ │ ├── 2.jpg
│ │ │ ├── 3.jpg
│ │ │ ├── 4.jpg
│ │ │ └── 5.jpg
│ └── short_description.txt
│ ├── zh-CN
│ ├── full_description.txt
│ └── short_description.txt
│ └── zh-TW
│ ├── full_description.txt
│ └── short_description.txt
└── screenshot
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
└── 5.jpg
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: HenryQuan
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://www.paypal.me/yihengquan
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | animeone/ios/Flutter/flutter_export_environment.sh
3 | animeone/.flutter-plugins-dependencies
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 - 2020 Yiheng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
AnimeOne
5 |
6 | ***AnimeOne不是官方 APP***。這是我使用 Flutter 製作的第三方 APP。第一個 React Native 的 APP 是用來看動漫的,第一個 Flutter 的 APP 也是用來看動漫的。これは運命かもしれない。不久之前,我學習了 [COMP2511](https://www.handbook.unsw.edu.au/undergraduate/courses/2019/COMP2511/),正好用這個 App 來練手。
7 | 總的來講,Flutter 非常好用,比起 React Native 我覺得最大的進步就是編譯方面。我沒有一次會因爲編譯不通過而苦惱。大多數時間可以專心寫代碼而且調試也非常棒。
8 |
9 | If you prefer watching anime with English subtitles, you might consider [AnimeGo](https://github.com/HenryQuan/AnimeGo).
10 |
11 |
12 | ## 功能
13 | - 最新動畫
14 | - 動畫列表
15 | - 快速搜索
16 | - 新番時間表
17 | - 新番介紹視頻
18 | - 内置視頻播放器
19 | - 自動軟件更新
20 | - 自動夜間模式
21 |
22 | ## 關於
23 | 目前版本已經可以正常使用,有任何問題的話可以使用 issues。
24 | 有一些數據會到本地本地(每七天會更新一次數據,1月/4月/7月/10月1日會自動更新)。
25 | 這是因爲動畫列表以及新番時間表并不會每天都更新,
26 | 所以 APP 也沒有理由每次開打都重新下載一遍數據。
27 |
28 | ## **年齡限制**
29 | 最近因爲某異世界 xxx 評鑑指南的播出,雖然沒有官方的分級審核,但還是決定為 AnimeOne 增加年齡限制。本 App 至少需要 15 歲(建議18嵗)才可以使用本 App,如果你不到 15 嵗請立即刪除本 App。
30 |
31 | ## **安卓權限**
32 | AnimeOne 不需要任何權限(除網絡連接)
33 |
34 | ## **隱私條款**
35 | AnimeOne 本身不會收集用戶的任何數據,
36 | GitHub 會紀錄 APK 下載次數以及頁面觀看次數和 Clone 次數。
37 |
38 | ## 截圖 (桌面版 1.0.7)
39 |
48 |
49 | # 相關 App
50 | - splitline 大神製作的 [emina-one](https://github.com/splitline/emina-one)
51 |
52 | ## 下載安裝
53 | 安卓可以在[這裏下載](https://github.com/HenryQuan/AnimeOne/releases/latest),IOS 則需要自己使用 Xcode 進行編譯或者[下載 IPA 安裝包](https://github.com/HenryQuan/AnimeOne/releases/latest)。
54 |
55 | [
](https://f-droid.org/packages/org.github.henryquan.animeone)
58 |
59 | ### Xcode 如何編譯
60 | - 在電腦上安裝 Flutter 以及 Xcode
61 | - 使用 `flutter doctor` 指令來檢查是否設置成功
62 | - clone 這個 repo
63 | - 進入 `animeone` 文件夾(注意是小寫)
64 | - 使用 `flutter build ios --release` 獲得 release 包(如果代碼沒有變化的話,只需要運行一次)
65 | - 進入 Xcode 打開 ios 文件夾下的 `Runner.xcworkspace`
66 | - 在暫停按鈕旁邊選擇 `RunnerRelease` (如果沒有的話,需要修改 Runner 的 schema?,把 debug 變成 release 即可)
67 | - 連接 IOS 設備(十分重要)
68 | - 點擊運行按鈕 -> 等待 Xcode 編譯 -> App 會自動安裝到設備並且打開
69 | - 需要每七天重複一次(開發者帳號是一直有效的)
70 |
71 | ### [桌面版](https://github.com/HenryQuan/AnimeOne/tree/master/animeone/desktop#%E5%A6%82%E4%BD%95%E7%B7%A8%E8%AD%AF%E6%A1%8C%E9%9D%A2%E7%89%88)
72 | 目前還在Alpha階段,將會支持 Linux、Mac 以及 Windows。
73 |
74 | ### 網頁版
75 | 暫無(估計也不會有了吧)
76 |
77 | ## 支持
78 | - 給 Repo 一顆星星
79 | - 上面的 Sponsor 按鈕(捐助)
80 | - App 内的捐助
81 |
--------------------------------------------------------------------------------
/animeone/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .packages
28 | .pub-cache/
29 | .pub/
30 | /build/
31 |
32 | **/android/app/release/
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 |
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Generated.xcconfig
62 | **/ios/Flutter/app.flx
63 | **/ios/Flutter/app.zip
64 | **/ios/Flutter/flutter_assets/
65 | **/ios/ServiceDefinitions.json
66 | **/ios/Runner/GeneratedPluginRegistrant.*
67 |
68 | # Exceptions to above rules.
69 | !**/ios/**/default.mode1v3
70 | !**/ios/**/default.mode2v3
71 | !**/ios/**/default.pbxuser
72 | !**/ios/**/default.perspectivev3
73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
74 |
--------------------------------------------------------------------------------
/animeone/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 20e59316b8b8474554b38493b8ca888794b0234a
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/animeone/README.md:
--------------------------------------------------------------------------------
1 | # animeone
2 |
3 | An unofficial app for anime1.me
4 |
5 | ## How to run it
6 | 1. Setup flutter on your computer
7 | 2. `flutter doctor` and do what's suggested
8 | 3. Connect your device and `flutter run`
9 |
--------------------------------------------------------------------------------
/animeone/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/animeone/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/animeone/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '118'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.1.8'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 32
30 | compileSdkVersion flutter.compileSdkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "org.github.henryquan.animeone"
48 | minSdkVersion 19
49 | targetSdkVersion 32
50 | targetSdkVersion flutter.targetSdkVersion
51 | versionCode flutterVersionCode.toInteger()
52 | versionName flutterVersionName
53 | }
54 |
55 | buildTypes {
56 | release {
57 | // TODO: Add your own signing config for the release build.
58 | // Signing with the debug keys for now, so `flutter run --release` works.
59 | signingConfig signingConfigs.debug
60 | }
61 | }
62 | }
63 |
64 | flutter {
65 | source '../..'
66 | }
67 |
68 | dependencies {
69 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
70 | implementation 'androidx.appcompat:appcompat:1.4.1'
71 | }
72 |
--------------------------------------------------------------------------------
/animeone/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
11 |
19 |
23 |
26 |
27 |
28 |
29 |
30 |
31 |
36 |
37 |
38 |
39 |
40 |
42 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/kotlin/org/github/henryquan/animeone/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package org.github.henryquan.animeone
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.os.PersistableBundle
6 | import androidx.annotation.NonNull
7 | import io.flutter.embedding.android.FlutterActivity
8 | import io.flutter.embedding.engine.FlutterEngine
9 | import io.flutter.plugin.common.MethodCall
10 | import io.flutter.plugin.common.MethodChannel
11 | import io.flutter.plugins.GeneratedPluginRegistrant
12 |
13 | class MainActivity : FlutterActivity() {
14 |
15 | private val animeOneChannel = "org.github.henryquan.animeone"
16 | private val webRequestCode = 1111
17 | private var methodResult: MethodChannel.Result? = null
18 |
19 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
20 | GeneratedPluginRegistrant.registerWith(flutterEngine)
21 |
22 | // Add method channel to receive calls from Flutter side
23 | MethodChannel(
24 | flutterEngine.dartExecutor.binaryMessenger,
25 | animeOneChannel
26 | ).setMethodCallHandler { call, result ->
27 | // Note: this method is invoked on the main thread
28 | when (call.method) {
29 | "getAnimeOneCookie" -> {
30 | methodResult = result
31 | val link = call.argument("link")!!
32 | bypassBrowserCheck(link)
33 | }
34 | "restartAnimeOne" -> restart()
35 | else -> result.notImplemented()
36 | }
37 | }
38 | }
39 |
40 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
41 | if (resultCode == webRequestCode) {
42 | val cookie = data?.getStringExtra("cookie")
43 | val agent = data?.getStringExtra("agent")
44 | methodResult?.success(listOf(cookie, agent))
45 | }
46 | }
47 |
48 | private fun bypassBrowserCheck(link: String) {
49 | // Grab the cookie for anime1.me
50 | val webIntent = Intent(context, WebActivity::class.java)
51 | webIntent.putExtra("link", link)
52 | startActivityForResult(webIntent, webRequestCode)
53 | }
54 |
55 | private fun restart() {
56 | this.finish()
57 | this.startActivity(intent)
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/kotlin/org/github/henryquan/animeone/WebActivity.kt:
--------------------------------------------------------------------------------
1 | package org.github.henryquan.animeone
2 |
3 | import android.content.Intent
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.webkit.CookieManager
7 | import android.webkit.WebView
8 | import android.webkit.WebViewClient
9 | import androidx.appcompat.app.AppCompatActivity
10 |
11 | class WebActivity : AppCompatActivity() {
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_webview)
16 | val link = intent.getStringExtra("link")!!
17 | // Clear cookies to get
18 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
19 | CookieManager.getInstance().removeAllCookies {
20 | println("Cookies are removed, $it")
21 | }
22 | }
23 |
24 | // Load the web view loads anime1.me
25 | val webView = findViewById(R.id.webView)
26 | webView.settings.javaScriptEnabled = true
27 | webView.clearCache(false)
28 | // Set up client to get cookie
29 | val client = WebClient(this)
30 | webView.webViewClient = client
31 | // Load whichever page that needs cookie
32 | webView.loadUrl(link)
33 | }
34 | }
35 |
36 | class WebClient(private val activity: AppCompatActivity) : WebViewClient() {
37 |
38 | override fun onPageFinished(view: WebView?, url: String?) {
39 | super.onPageFinished(view, url)
40 | view?.evaluateJavascript(
41 | """(function() {
42 | return "" + document.getElementsByTagName('html')[0].innerHTML + "";
43 | })()""".trimMargin()
44 | ) {
45 | // Make sure the checking view has passed
46 | if (!it.contains("Checking your browser before accessing")) {
47 | val userAgent = view.settings.userAgentString
48 | val cookie = CookieManager.getInstance().getCookie(url)
49 |
50 | // free the web view properly here
51 | view.stopLoading()
52 | view.onPause()
53 | view.removeAllViews()
54 |
55 | val main = Intent(this.activity, MainActivity::class.java)
56 | main.putExtra("cookie", cookie)
57 | main.putExtra("agent", userAgent)
58 | this.activity.setResult(1111, main)
59 | this.activity.finish()
60 | println("cookie fixed")
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/layout/activity_webview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
18 |
24 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 正在檢查你的瀏覽器\n請稍候\n檢查完成會自動導向網站
4 |
--------------------------------------------------------------------------------
/animeone/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/animeone/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/animeone/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.20'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/animeone/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx2048M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/animeone/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
7 |
--------------------------------------------------------------------------------
/animeone/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/animeone/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/animeone/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/animeone/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/animeone/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/animeone/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/animeone/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - share (0.0.1):
4 | - Flutter
5 | - shared_preferences_ios (0.0.1):
6 | - Flutter
7 | - url_launcher_ios (0.0.1):
8 | - Flutter
9 | - webview_flutter_wkwebview (0.0.1):
10 | - Flutter
11 |
12 | DEPENDENCIES:
13 | - Flutter (from `Flutter`)
14 | - share (from `.symlinks/plugins/share/ios`)
15 | - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
16 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
17 | - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
18 |
19 | EXTERNAL SOURCES:
20 | Flutter:
21 | :path: Flutter
22 | share:
23 | :path: ".symlinks/plugins/share/ios"
24 | shared_preferences_ios:
25 | :path: ".symlinks/plugins/shared_preferences_ios/ios"
26 | url_launcher_ios:
27 | :path: ".symlinks/plugins/url_launcher_ios/ios"
28 | webview_flutter_wkwebview:
29 | :path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
30 |
31 | SPEC CHECKSUMS:
32 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
33 | share: 0b2c3e82132f5888bccca3351c504d0003b3b410
34 | shared_preferences_ios: aef470a42dc4675a1cdd50e3158b42e3d1232b32
35 | url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
36 | webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162
37 |
38 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
39 |
40 | COCOAPODS: 1.11.2
41 |
--------------------------------------------------------------------------------
/animeone/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 4C71EFF3534C780520EA8878 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CB93B6CDF152AABAD441BD7 /* Pods_Runner.framework */; };
13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXCopyFilesBuildPhase section */
20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
21 | isa = PBXCopyFilesBuildPhase;
22 | buildActionMask = 2147483647;
23 | dstPath = "";
24 | dstSubfolderSpec = 10;
25 | files = (
26 | );
27 | name = "Embed Frameworks";
28 | runOnlyForDeploymentPostprocessing = 0;
29 | };
30 | /* End PBXCopyFilesBuildPhase section */
31 |
32 | /* Begin PBXFileReference section */
33 | 0CB93B6CDF152AABAD441BD7 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
34 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
35 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
37 | 712144E1E89D804704193CDA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
38 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
39 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | B5DA6BD99E053193D125BE59 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
49 | E800179DDA99F7ED7BFAC0B6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
50 | /* End PBXFileReference section */
51 |
52 | /* Begin PBXFrameworksBuildPhase section */
53 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
54 | isa = PBXFrameworksBuildPhase;
55 | buildActionMask = 2147483647;
56 | files = (
57 | 4C71EFF3534C780520EA8878 /* Pods_Runner.framework in Frameworks */,
58 | );
59 | runOnlyForDeploymentPostprocessing = 0;
60 | };
61 | /* End PBXFrameworksBuildPhase section */
62 |
63 | /* Begin PBXGroup section */
64 | 2C7591C41E7C76ED873EE148 /* Frameworks */ = {
65 | isa = PBXGroup;
66 | children = (
67 | 0CB93B6CDF152AABAD441BD7 /* Pods_Runner.framework */,
68 | );
69 | name = Frameworks;
70 | sourceTree = "";
71 | };
72 | 9740EEB11CF90186004384FC /* Flutter */ = {
73 | isa = PBXGroup;
74 | children = (
75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
76 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
77 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
78 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
79 | );
80 | name = Flutter;
81 | sourceTree = "";
82 | };
83 | 97C146E51CF9000F007C117D = {
84 | isa = PBXGroup;
85 | children = (
86 | 9740EEB11CF90186004384FC /* Flutter */,
87 | 97C146F01CF9000F007C117D /* Runner */,
88 | 97C146EF1CF9000F007C117D /* Products */,
89 | 99F721C84408411F59A8B3EB /* Pods */,
90 | 2C7591C41E7C76ED873EE148 /* Frameworks */,
91 | );
92 | sourceTree = "";
93 | };
94 | 97C146EF1CF9000F007C117D /* Products */ = {
95 | isa = PBXGroup;
96 | children = (
97 | 97C146EE1CF9000F007C117D /* Runner.app */,
98 | );
99 | name = Products;
100 | sourceTree = "";
101 | };
102 | 97C146F01CF9000F007C117D /* Runner */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
106 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
107 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
108 | 97C147021CF9000F007C117D /* Info.plist */,
109 | 97C146F11CF9000F007C117D /* Supporting Files */,
110 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
111 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
112 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
113 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
114 | );
115 | path = Runner;
116 | sourceTree = "";
117 | };
118 | 97C146F11CF9000F007C117D /* Supporting Files */ = {
119 | isa = PBXGroup;
120 | children = (
121 | );
122 | name = "Supporting Files";
123 | sourceTree = "";
124 | };
125 | 99F721C84408411F59A8B3EB /* Pods */ = {
126 | isa = PBXGroup;
127 | children = (
128 | B5DA6BD99E053193D125BE59 /* Pods-Runner.debug.xcconfig */,
129 | E800179DDA99F7ED7BFAC0B6 /* Pods-Runner.release.xcconfig */,
130 | 712144E1E89D804704193CDA /* Pods-Runner.profile.xcconfig */,
131 | );
132 | path = Pods;
133 | sourceTree = "";
134 | };
135 | /* End PBXGroup section */
136 |
137 | /* Begin PBXNativeTarget section */
138 | 97C146ED1CF9000F007C117D /* Runner */ = {
139 | isa = PBXNativeTarget;
140 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
141 | buildPhases = (
142 | A2A82CB31C8D270DC8861D9B /* [CP] Check Pods Manifest.lock */,
143 | 9740EEB61CF901F6004384FC /* Run Script */,
144 | 97C146EA1CF9000F007C117D /* Sources */,
145 | 97C146EB1CF9000F007C117D /* Frameworks */,
146 | 97C146EC1CF9000F007C117D /* Resources */,
147 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
148 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
149 | 83375E720E24BBDDD81647A0 /* [CP] Embed Pods Frameworks */,
150 | );
151 | buildRules = (
152 | );
153 | dependencies = (
154 | );
155 | name = Runner;
156 | productName = Runner;
157 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
158 | productType = "com.apple.product-type.application";
159 | };
160 | /* End PBXNativeTarget section */
161 |
162 | /* Begin PBXProject section */
163 | 97C146E61CF9000F007C117D /* Project object */ = {
164 | isa = PBXProject;
165 | attributes = {
166 | LastUpgradeCheck = 1300;
167 | ORGANIZATIONNAME = "The Chromium Authors";
168 | TargetAttributes = {
169 | 97C146ED1CF9000F007C117D = {
170 | CreatedOnToolsVersion = 7.3.1;
171 | DevelopmentTeam = 4YSG3SYMLE;
172 | LastSwiftMigration = 1100;
173 | };
174 | };
175 | };
176 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
177 | compatibilityVersion = "Xcode 3.2";
178 | developmentRegion = en;
179 | hasScannedForEncodings = 0;
180 | knownRegions = (
181 | en,
182 | Base,
183 | );
184 | mainGroup = 97C146E51CF9000F007C117D;
185 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
186 | projectDirPath = "";
187 | projectRoot = "";
188 | targets = (
189 | 97C146ED1CF9000F007C117D /* Runner */,
190 | );
191 | };
192 | /* End PBXProject section */
193 |
194 | /* Begin PBXResourcesBuildPhase section */
195 | 97C146EC1CF9000F007C117D /* Resources */ = {
196 | isa = PBXResourcesBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
200 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
201 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
202 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
203 | );
204 | runOnlyForDeploymentPostprocessing = 0;
205 | };
206 | /* End PBXResourcesBuildPhase section */
207 |
208 | /* Begin PBXShellScriptBuildPhase section */
209 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
210 | isa = PBXShellScriptBuildPhase;
211 | buildActionMask = 2147483647;
212 | files = (
213 | );
214 | inputPaths = (
215 | );
216 | name = "Thin Binary";
217 | outputPaths = (
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | shellPath = /bin/sh;
221 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
222 | };
223 | 83375E720E24BBDDD81647A0 /* [CP] Embed Pods Frameworks */ = {
224 | isa = PBXShellScriptBuildPhase;
225 | buildActionMask = 2147483647;
226 | files = (
227 | );
228 | inputPaths = (
229 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
230 | "${BUILT_PRODUCTS_DIR}/share/share.framework",
231 | "${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework",
232 | "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
233 | "${BUILT_PRODUCTS_DIR}/webview_flutter_wkwebview/webview_flutter_wkwebview.framework",
234 | );
235 | name = "[CP] Embed Pods Frameworks";
236 | outputPaths = (
237 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework",
238 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework",
239 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
240 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/webview_flutter_wkwebview.framework",
241 | );
242 | runOnlyForDeploymentPostprocessing = 0;
243 | shellPath = /bin/sh;
244 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
245 | showEnvVarsInLog = 0;
246 | };
247 | 9740EEB61CF901F6004384FC /* Run Script */ = {
248 | isa = PBXShellScriptBuildPhase;
249 | buildActionMask = 2147483647;
250 | files = (
251 | );
252 | inputPaths = (
253 | );
254 | name = "Run Script";
255 | outputPaths = (
256 | );
257 | runOnlyForDeploymentPostprocessing = 0;
258 | shellPath = /bin/sh;
259 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
260 | };
261 | A2A82CB31C8D270DC8861D9B /* [CP] Check Pods Manifest.lock */ = {
262 | isa = PBXShellScriptBuildPhase;
263 | buildActionMask = 2147483647;
264 | files = (
265 | );
266 | inputFileListPaths = (
267 | );
268 | inputPaths = (
269 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
270 | "${PODS_ROOT}/Manifest.lock",
271 | );
272 | name = "[CP] Check Pods Manifest.lock";
273 | outputFileListPaths = (
274 | );
275 | outputPaths = (
276 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
277 | );
278 | runOnlyForDeploymentPostprocessing = 0;
279 | shellPath = /bin/sh;
280 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
281 | showEnvVarsInLog = 0;
282 | };
283 | /* End PBXShellScriptBuildPhase section */
284 |
285 | /* Begin PBXSourcesBuildPhase section */
286 | 97C146EA1CF9000F007C117D /* Sources */ = {
287 | isa = PBXSourcesBuildPhase;
288 | buildActionMask = 2147483647;
289 | files = (
290 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
291 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | /* End PBXSourcesBuildPhase section */
296 |
297 | /* Begin PBXVariantGroup section */
298 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
299 | isa = PBXVariantGroup;
300 | children = (
301 | 97C146FB1CF9000F007C117D /* Base */,
302 | );
303 | name = Main.storyboard;
304 | sourceTree = "";
305 | };
306 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
307 | isa = PBXVariantGroup;
308 | children = (
309 | 97C147001CF9000F007C117D /* Base */,
310 | );
311 | name = LaunchScreen.storyboard;
312 | sourceTree = "";
313 | };
314 | /* End PBXVariantGroup section */
315 |
316 | /* Begin XCBuildConfiguration section */
317 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
318 | isa = XCBuildConfiguration;
319 | buildSettings = {
320 | ALWAYS_SEARCH_USER_PATHS = NO;
321 | CLANG_ANALYZER_NONNULL = YES;
322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
323 | CLANG_CXX_LIBRARY = "libc++";
324 | CLANG_ENABLE_MODULES = YES;
325 | CLANG_ENABLE_OBJC_ARC = YES;
326 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
327 | CLANG_WARN_BOOL_CONVERSION = YES;
328 | CLANG_WARN_COMMA = YES;
329 | CLANG_WARN_CONSTANT_CONVERSION = YES;
330 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
332 | CLANG_WARN_EMPTY_BODY = YES;
333 | CLANG_WARN_ENUM_CONVERSION = YES;
334 | CLANG_WARN_INFINITE_RECURSION = YES;
335 | CLANG_WARN_INT_CONVERSION = YES;
336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
337 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
340 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
341 | CLANG_WARN_STRICT_PROTOTYPES = YES;
342 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
343 | CLANG_WARN_UNREACHABLE_CODE = YES;
344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
345 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
346 | COPY_PHASE_STRIP = NO;
347 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
348 | ENABLE_NS_ASSERTIONS = NO;
349 | ENABLE_STRICT_OBJC_MSGSEND = YES;
350 | GCC_C_LANGUAGE_STANDARD = gnu99;
351 | GCC_NO_COMMON_BLOCKS = YES;
352 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
353 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
354 | GCC_WARN_UNDECLARED_SELECTOR = YES;
355 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
356 | GCC_WARN_UNUSED_FUNCTION = YES;
357 | GCC_WARN_UNUSED_VARIABLE = YES;
358 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
359 | MTL_ENABLE_DEBUG_INFO = NO;
360 | SDKROOT = iphoneos;
361 | SUPPORTED_PLATFORMS = iphoneos;
362 | TARGETED_DEVICE_FAMILY = "1,2";
363 | VALIDATE_PRODUCT = YES;
364 | };
365 | name = Profile;
366 | };
367 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
368 | isa = XCBuildConfiguration;
369 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
370 | buildSettings = {
371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
372 | CLANG_ENABLE_MODULES = YES;
373 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
374 | DEVELOPMENT_TEAM = 4YSG3SYMLE;
375 | ENABLE_BITCODE = NO;
376 | FRAMEWORK_SEARCH_PATHS = (
377 | "$(inherited)",
378 | "$(PROJECT_DIR)/Flutter",
379 | );
380 | INFOPLIST_FILE = Runner/Info.plist;
381 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
382 | LD_RUNPATH_SEARCH_PATHS = (
383 | "$(inherited)",
384 | "@executable_path/Frameworks",
385 | );
386 | LIBRARY_SEARCH_PATHS = (
387 | "$(inherited)",
388 | "$(PROJECT_DIR)/Flutter",
389 | );
390 | PRODUCT_BUNDLE_IDENTIFIER = org.github.henryquan.animeone;
391 | PRODUCT_NAME = "$(TARGET_NAME)";
392 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
393 | SWIFT_VERSION = 5.0;
394 | VERSIONING_SYSTEM = "apple-generic";
395 | };
396 | name = Profile;
397 | };
398 | 97C147031CF9000F007C117D /* Debug */ = {
399 | isa = XCBuildConfiguration;
400 | buildSettings = {
401 | ALWAYS_SEARCH_USER_PATHS = NO;
402 | CLANG_ANALYZER_NONNULL = YES;
403 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
404 | CLANG_CXX_LIBRARY = "libc++";
405 | CLANG_ENABLE_MODULES = YES;
406 | CLANG_ENABLE_OBJC_ARC = YES;
407 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
408 | CLANG_WARN_BOOL_CONVERSION = YES;
409 | CLANG_WARN_COMMA = YES;
410 | CLANG_WARN_CONSTANT_CONVERSION = YES;
411 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
412 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
413 | CLANG_WARN_EMPTY_BODY = YES;
414 | CLANG_WARN_ENUM_CONVERSION = YES;
415 | CLANG_WARN_INFINITE_RECURSION = YES;
416 | CLANG_WARN_INT_CONVERSION = YES;
417 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
418 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
419 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
420 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
421 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
422 | CLANG_WARN_STRICT_PROTOTYPES = YES;
423 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
424 | CLANG_WARN_UNREACHABLE_CODE = YES;
425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
427 | COPY_PHASE_STRIP = NO;
428 | DEBUG_INFORMATION_FORMAT = dwarf;
429 | ENABLE_STRICT_OBJC_MSGSEND = YES;
430 | ENABLE_TESTABILITY = YES;
431 | GCC_C_LANGUAGE_STANDARD = gnu99;
432 | GCC_DYNAMIC_NO_PIC = NO;
433 | GCC_NO_COMMON_BLOCKS = YES;
434 | GCC_OPTIMIZATION_LEVEL = 0;
435 | GCC_PREPROCESSOR_DEFINITIONS = (
436 | "DEBUG=1",
437 | "$(inherited)",
438 | );
439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
441 | GCC_WARN_UNDECLARED_SELECTOR = YES;
442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
443 | GCC_WARN_UNUSED_FUNCTION = YES;
444 | GCC_WARN_UNUSED_VARIABLE = YES;
445 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
446 | MTL_ENABLE_DEBUG_INFO = YES;
447 | ONLY_ACTIVE_ARCH = YES;
448 | SDKROOT = iphoneos;
449 | TARGETED_DEVICE_FAMILY = "1,2";
450 | };
451 | name = Debug;
452 | };
453 | 97C147041CF9000F007C117D /* Release */ = {
454 | isa = XCBuildConfiguration;
455 | buildSettings = {
456 | ALWAYS_SEARCH_USER_PATHS = NO;
457 | CLANG_ANALYZER_NONNULL = YES;
458 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
459 | CLANG_CXX_LIBRARY = "libc++";
460 | CLANG_ENABLE_MODULES = YES;
461 | CLANG_ENABLE_OBJC_ARC = YES;
462 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
463 | CLANG_WARN_BOOL_CONVERSION = YES;
464 | CLANG_WARN_COMMA = YES;
465 | CLANG_WARN_CONSTANT_CONVERSION = YES;
466 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
467 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
468 | CLANG_WARN_EMPTY_BODY = YES;
469 | CLANG_WARN_ENUM_CONVERSION = YES;
470 | CLANG_WARN_INFINITE_RECURSION = YES;
471 | CLANG_WARN_INT_CONVERSION = YES;
472 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
473 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
474 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
475 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
476 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
477 | CLANG_WARN_STRICT_PROTOTYPES = YES;
478 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
479 | CLANG_WARN_UNREACHABLE_CODE = YES;
480 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
481 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
482 | COPY_PHASE_STRIP = NO;
483 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
484 | ENABLE_NS_ASSERTIONS = NO;
485 | ENABLE_STRICT_OBJC_MSGSEND = YES;
486 | GCC_C_LANGUAGE_STANDARD = gnu99;
487 | GCC_NO_COMMON_BLOCKS = YES;
488 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
489 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
490 | GCC_WARN_UNDECLARED_SELECTOR = YES;
491 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
492 | GCC_WARN_UNUSED_FUNCTION = YES;
493 | GCC_WARN_UNUSED_VARIABLE = YES;
494 | IPHONEOS_DEPLOYMENT_TARGET = 14.5;
495 | MTL_ENABLE_DEBUG_INFO = NO;
496 | SDKROOT = iphoneos;
497 | SUPPORTED_PLATFORMS = iphoneos;
498 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
499 | TARGETED_DEVICE_FAMILY = "1,2";
500 | VALIDATE_PRODUCT = YES;
501 | };
502 | name = Release;
503 | };
504 | 97C147061CF9000F007C117D /* Debug */ = {
505 | isa = XCBuildConfiguration;
506 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
507 | buildSettings = {
508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
509 | CLANG_ENABLE_MODULES = YES;
510 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
511 | DEVELOPMENT_TEAM = 4YSG3SYMLE;
512 | ENABLE_BITCODE = NO;
513 | FRAMEWORK_SEARCH_PATHS = (
514 | "$(inherited)",
515 | "$(PROJECT_DIR)/Flutter",
516 | );
517 | INFOPLIST_FILE = Runner/Info.plist;
518 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
519 | LD_RUNPATH_SEARCH_PATHS = (
520 | "$(inherited)",
521 | "@executable_path/Frameworks",
522 | );
523 | LIBRARY_SEARCH_PATHS = (
524 | "$(inherited)",
525 | "$(PROJECT_DIR)/Flutter",
526 | );
527 | PRODUCT_BUNDLE_IDENTIFIER = org.github.henryquan.animeone;
528 | PRODUCT_NAME = "$(TARGET_NAME)";
529 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
530 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
531 | SWIFT_VERSION = 5.0;
532 | VERSIONING_SYSTEM = "apple-generic";
533 | };
534 | name = Debug;
535 | };
536 | 97C147071CF9000F007C117D /* Release */ = {
537 | isa = XCBuildConfiguration;
538 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
539 | buildSettings = {
540 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
541 | CLANG_ENABLE_MODULES = YES;
542 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
543 | DEVELOPMENT_TEAM = 4YSG3SYMLE;
544 | ENABLE_BITCODE = NO;
545 | FRAMEWORK_SEARCH_PATHS = (
546 | "$(inherited)",
547 | "$(PROJECT_DIR)/Flutter",
548 | );
549 | INFOPLIST_FILE = Runner/Info.plist;
550 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
551 | LD_RUNPATH_SEARCH_PATHS = (
552 | "$(inherited)",
553 | "@executable_path/Frameworks",
554 | );
555 | LIBRARY_SEARCH_PATHS = (
556 | "$(inherited)",
557 | "$(PROJECT_DIR)/Flutter",
558 | );
559 | PRODUCT_BUNDLE_IDENTIFIER = org.github.henryquan.animeone;
560 | PRODUCT_NAME = "$(TARGET_NAME)";
561 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
562 | SWIFT_VERSION = 5.0;
563 | VERSIONING_SYSTEM = "apple-generic";
564 | };
565 | name = Release;
566 | };
567 | /* End XCBuildConfiguration section */
568 |
569 | /* Begin XCConfigurationList section */
570 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
571 | isa = XCConfigurationList;
572 | buildConfigurations = (
573 | 97C147031CF9000F007C117D /* Debug */,
574 | 97C147041CF9000F007C117D /* Release */,
575 | 249021D3217E4FDB00AE95B9 /* Profile */,
576 | );
577 | defaultConfigurationIsVisible = 0;
578 | defaultConfigurationName = Release;
579 | };
580 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
581 | isa = XCConfigurationList;
582 | buildConfigurations = (
583 | 97C147061CF9000F007C117D /* Debug */,
584 | 97C147071CF9000F007C117D /* Release */,
585 | 249021D4217E4FDB00AE95B9 /* Profile */,
586 | );
587 | defaultConfigurationIsVisible = 0;
588 | defaultConfigurationName = Release;
589 | };
590 | /* End XCConfigurationList section */
591 | };
592 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
593 | }
594 |
--------------------------------------------------------------------------------
/animeone/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/animeone/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/animeone/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/animeone/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/animeone/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon_20pt@2_40x40-1.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon_20pt@3_60x60.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon_29pt@1_29x29-1.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon_29pt@2_58x58-1.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon_29pt@3_87x87.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon_40pt@2_80x80-1.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon_40pt@3_120x120.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon_60pt@2_120x120.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon_60pt@3_180x180.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon_20pt@1_20x20.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon_20pt@2_40x40.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon_29pt@1_29x29.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon_29pt@2_58x58.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon_40pt@1_40x40.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon_40pt@2_80x80.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon_76pt@1_76x76.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon_76pt@2_152x152.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon_83.5pt@2_167x167.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "_AppIcon_1024x1024.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@1_20x20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@1_20x20.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@2_40x40-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@2_40x40-1.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@2_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@2_40x40.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@3_60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_20pt@3_60x60.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@1_29x29-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@1_29x29-1.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@1_29x29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@1_29x29.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@2_58x58-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@2_58x58-1.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@2_58x58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@2_58x58.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@3_87x87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_29pt@3_87x87.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@1_40x40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@1_40x40.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@2_80x80-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@2_80x80-1.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@2_80x80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@2_80x80.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@3_120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_40pt@3_120x120.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_60pt@2_120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_60pt@2_120x120.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_60pt@3_180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_60pt@3_180x180.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_76pt@1_76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_76pt@1_76x76.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_76pt@2_152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_76pt@2_152x152.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_83.5pt@2_167x167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon_83.5pt@2_167x167.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/_AppIcon_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/AppIcon.appiconset/_AppIcon_1024x1024.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/animeone/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/animeone/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/animeone/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/animeone/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | AnimeOne
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | animeone
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/animeone/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/animeone/lib/WebsiteClosed.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:url_launcher/url_launcher.dart';
3 |
4 | /// WebsiteClosed class
5 | class WebsiteClosed extends StatelessWidget {
6 | WebsiteClosed({Key? key}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Scaffold(
11 | body: Center(
12 | child: Column(
13 | mainAxisAlignment: MainAxisAlignment.center,
14 | children: [
15 | Image(
16 | image: AssetImage('lib/assets/icon/logo.png'),
17 | height: 200,
18 | width: 200,
19 | color: Colors.pink,
20 | ),
21 | Text('網站已關閉', style: Theme.of(context).textTheme.headline4),
22 | Padding(
23 | padding: const EdgeInsets.only(bottom: 32),
24 | child: Text('「楓林網」遭查封'),
25 | ),
26 | Padding(
27 | padding: const EdgeInsets.only(bottom: 16),
28 | child: Text(
29 | 'AnimeOne 並不是官方的應用程式,\n這只是我個人的開源項目,\n因為網站已經關閉所以開發已終止。',
30 | textAlign: TextAlign.center,
31 | ),
32 | ),
33 | Text('如果你喜歡英文字幕的話'),
34 | ElevatedButton(
35 | onPressed: () {
36 | launch('https://github.com/HenryQuan/AnimeGo-Re/releases');
37 | },
38 | child: Text('下載 AnimeGo'),
39 | ),
40 | Padding(
41 | padding: const EdgeInsets.only(top: 32),
42 | child: TextButton(
43 | onPressed: () {
44 | launch('https://github.com/HenryQuan/AnimeOne');
45 | },
46 | child: Text('https://github.com/HenryQuan/AnimeOne'),
47 | ),
48 | ),
49 | Text(
50 | 'Aug 2019 - Apr 2020',
51 | textAlign: TextAlign.center,
52 | )
53 | ],
54 | ),
55 | ),
56 | );
57 | }
58 | }
59 |
60 | //
61 | // 永遠のAnimeOne
62 |
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/3.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/4.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/5.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/6.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/7.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/8.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/cover/9.jpg
--------------------------------------------------------------------------------
/animeone/lib/assets/cover/README.md:
--------------------------------------------------------------------------------
1 | # Cover
2 | All of them are from [anime1.me](https:/anime1.me).
3 | Please tell me if you want me to remove them.
4 |
--------------------------------------------------------------------------------
/animeone/lib/assets/icon/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HQAnime/AnimeOne/bfa82957d80ff6eb28550c31d22103c40a269d0d/animeone/lib/assets/icon/logo.png
--------------------------------------------------------------------------------
/animeone/lib/core/AnimeOne.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:animeone/core/GlobalData.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/services.dart';
6 |
7 | /// This class communicates with native code
8 | class AnimeOne {
9 | // This is a channel to connect with native side
10 | static final nativeChannel = MethodChannel('org.github.henryquan.animeone');
11 |
12 | /// If native channel is supported
13 | bool _isSupported() {
14 | return Platform.isAndroid;
15 | }
16 |
17 | Future? _invokeMethod(String method, [dynamic arguments]) async {
18 | if (this._isSupported()) {
19 | return await nativeChannel.invokeMethod(method, arguments);
20 | }
21 |
22 | return null;
23 | }
24 |
25 | /// Restart the app
26 | Future? restartApp() async {
27 | return await this._invokeMethod('restartAnimeOne');
28 | }
29 |
30 | /// Popup native browser and get cookie from webview
31 | Future>? _getAnimeOneCookie() async {
32 | final list = await this._invokeMethod(
33 | 'getAnimeOneCookie',
34 | {'link': GlobalData.requestCookieLink},
35 | ) as List;
36 |
37 | return list.map((e) => e as String).toList();
38 | }
39 |
40 | void bypassWebsiteCheck(BuildContext context) {
41 | _getAnimeOneCookie()?.then((output) {
42 | final cookie = output[0];
43 | final userAgent = output[1];
44 | if (cookie.length > 0 && cookie.contains('cf_clearance')) {
45 | print(cookie);
46 | final data = GlobalData();
47 | data.updateCookie(cookie);
48 | data.updateUserAgent(userAgent);
49 |
50 | // restart if successful, only show the error if it failed
51 | restartApp();
52 | } else {
53 | showDialog(
54 | context: context,
55 | builder: (c) => AlertDialog(
56 | title: Text('修復失敗'),
57 | content: Text('請再次嘗試,如果連續三次都失敗的話,請查看詳細信息。'),
58 | actions: [
59 | TextButton(
60 | onPressed: () => Navigator.pop(context),
61 | child: Text('好的'),
62 | ),
63 | ],
64 | ),
65 | );
66 | }
67 | });
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/animeone/lib/core/GlobalData.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:animeone/core/anime/AnimeRecent.dart';
5 | import 'package:animeone/core/anime/AnimeSchedule.dart';
6 | import 'package:animeone/core/anime/AnimeSeason.dart';
7 | import 'package:animeone/core/anime/AnimeVideo.dart';
8 | import 'package:animeone/core/other/GithubUpdate.dart';
9 | import 'package:animeone/core/parser/AnimeListParserV2.dart';
10 | import 'package:animeone/core/parser/AnimeRecentParser.dart';
11 | import 'package:animeone/core/parser/AnimeScheduleParser.dart';
12 | import 'package:animeone/core/parser/GithubParser.dart';
13 | import 'package:html/dom.dart';
14 | import 'package:shared_preferences/shared_preferences.dart';
15 | import 'package:url_launcher/url_launcher.dart';
16 |
17 | import 'anime/AnimeInfo.dart';
18 |
19 | /// A class has constants and also a list of all anime
20 | class GlobalData {
21 | static final domain = 'https://anime1.me/';
22 | static final version = '1.1.8';
23 |
24 | static final githubRelease =
25 | 'https://raw.githubusercontent.com/HenryQuan/AnimeOne/api/app.json';
26 | static final latestRelease =
27 | 'https://github.com/HenryQuan/AnimeOne/releases/latest';
28 |
29 | static final eminaOne = 'https://github.com/splitline/emina-one';
30 | static final animeGo = 'https://github.com/HenryQuan/AnimeGo';
31 |
32 | /// if update has been checked
33 | bool hasUpdate = false;
34 |
35 | /// A flag to check if cookie is necessary
36 | static String? requestCookieLink = '';
37 |
38 | // Relating to local data
39 | late SharedPreferences prefs;
40 | static final lastUpdate = 'AnimeOne:LastUpdate';
41 | static final animeList = 'AnimeOne:AnimeList';
42 | static final animeScedule = 'AnimeOne:AnimeScedule';
43 | static final scheduleIntroVideo = 'AnimeOne:SceduleIntroVideo';
44 | static final oneCookie = 'AnimeOne:OneCookie';
45 | static final oneUserAgent = 'AnimeOne:OneUserAgent';
46 | static final ageRestriction = 'AnimeOne:AgeRestriction';
47 |
48 | // Relating to seasonal anime
49 | static final _season = new AnimeSeason(DateTime.now());
50 | String getSeasonName() => _season.toString();
51 | String getScheduleLink() => _season.getLink();
52 | String getSeasonLink() => _season.getAnimeLink();
53 | List getQuickFilters() => _season.getQuickFilters();
54 |
55 | // Relating to anime list (it won't be changed)
56 | List _animeList = [];
57 | List getAnimeList() => this._animeList;
58 | // Relating to anime scedule (it doesn't change as well)
59 | AnimeVideo? _introductory;
60 | AnimeVideo? getIntroVideo() => this._introductory;
61 | List _animeScheduleList = [];
62 | List getScheduleList() => this._animeScheduleList;
63 | // Relating to recent anime
64 | late AnimeRecentParser _recentParser;
65 | List _recentList = [];
66 | List getRecentList() => this._recentList;
67 | // Saved cookie for animeon
68 | String? _cookie;
69 | String? _userAgent;
70 |
71 | /// Use videopassword as the default cookie
72 | String getCookie() => _cookie ?? 'videopassword=0';
73 | void updateCookie(String cookie) {
74 | _cookie = cookie;
75 | // Add video password if not included
76 | if (!cookie.contains('videopassword')) {
77 | _cookie = _cookie! + '; videopassword=0';
78 | }
79 |
80 | prefs.setString(oneCookie, _cookie!);
81 | }
82 |
83 | String getUserAgent() => _userAgent ?? '';
84 | void updateUserAgent(String agent) {
85 | _userAgent = agent;
86 | prefs.setString(oneUserAgent, agent);
87 | }
88 |
89 | // Age restriction
90 | bool _showAgeAlert = false;
91 | bool showShowAgeAlert() => _showAgeAlert;
92 | void updateAgeAlert() {
93 | _showAgeAlert = false;
94 | prefs.setString(ageRestriction, jsonEncode(false));
95 | }
96 |
97 | // Relating to Github update
98 | GithubUpdate? _update;
99 | GithubUpdate? getGithubUpdate() => this._update;
100 |
101 | // Singleton pattern
102 | GlobalData._init();
103 | static final GlobalData _instance = new GlobalData._init();
104 |
105 | // Use dart's factory constructor to implement this patternx
106 | factory GlobalData() {
107 | return _instance;
108 | }
109 |
110 | /// Get data from anime1.me if necessary
111 | Future init() async {
112 | bool shouldUpdate = false;
113 |
114 | prefs = await SharedPreferences.getInstance();
115 | // Check if data are stored properly
116 | // prefs.getKeys().forEach((k) {
117 | // debugPrint('$k ${prefs.get(k)}');
118 | // });
119 |
120 | // Whether an age alert shoud be shown
121 | String? ageAlert = prefs.get(ageRestriction) as String?;
122 | if (ageAlert == null) {
123 | this._showAgeAlert = true;
124 | }
125 |
126 | // Get saved cookie
127 | String? savedCookie = prefs.getString(oneCookie);
128 | if (savedCookie != null) {
129 | this._cookie = savedCookie;
130 | print('Cookie - $savedCookie');
131 | }
132 |
133 | // Get saved user agent
134 | String? savedUserAgent = prefs.getString(oneUserAgent);
135 | if (savedUserAgent != null) {
136 | this._userAgent = savedUserAgent;
137 | print('User agent - $savedUserAgent');
138 | }
139 |
140 | // Check if this is the new version
141 | String? newVersion = prefs.getString(version);
142 | if (newVersion == null) {
143 | // Only update once when there is a new update
144 | prefs.setString(version, 'ok');
145 | shouldUpdate = true;
146 | }
147 |
148 | // Get last updated date
149 | String? update = prefs.getString(lastUpdate);
150 | if (update == null) {
151 | // Init update
152 | prefs.setString(lastUpdate, DateTime.now().toIso8601String());
153 | shouldUpdate = true;
154 | } else {
155 | final now = DateTime.now();
156 | if (now.day == 1) {
157 | // Update if today is 1st Jan/Apr/Jul/Oct
158 | final newSeasonMonth = [1, 4, 7, 10];
159 | if (newSeasonMonth.indexOf(now.month) > -1) {
160 | prefs.setString(lastUpdate, now.toIso8601String());
161 | shouldUpdate = true;
162 | }
163 | } else {
164 | final diff = now.difference(DateTime.parse(update));
165 | // Check for update once a wekk
166 | if (diff.inDays >= 7) {
167 | // Remember to save new date!
168 | prefs.setString(lastUpdate, now.toIso8601String());
169 | shouldUpdate = true;
170 | }
171 | }
172 | }
173 |
174 | // Get new data and save them locally
175 | if (shouldUpdate) {
176 | // Check for update first to make sure you don't messed up auto update
177 | await this.checkGithubUpdate();
178 |
179 | // Load anime list
180 | await this._getAnimeList();
181 |
182 | // Load anime schedule
183 | await this._getAnimeScedule();
184 | } else {
185 | // if anime list has been loaded but somehow, it failed
186 | // you need to reset the list so that it won't have duplicates
187 | this._resetList();
188 |
189 | // Load everything from storage
190 | final animeListJson = prefs.getString(animeList);
191 | if (animeListJson == null) {
192 | // cache it again if not found
193 | await this._getAnimeList();
194 | } else {
195 | List savedAnimeList = jsonDecode(animeListJson);
196 | savedAnimeList.forEach((json) {
197 | this._animeList.add(AnimeInfo.fromJson(json));
198 | });
199 | }
200 |
201 | // Same as anime list
202 | final scheduleListJson = prefs.getString(animeScedule);
203 | if (scheduleListJson == null) {
204 | // cache it again if not found
205 | await this._getAnimeScedule();
206 | } else {
207 | List savedScheduleList = jsonDecode(scheduleListJson);
208 | savedScheduleList.forEach((json) {
209 | this._animeScheduleList.add(AnimeSchedule.fromJson(json));
210 | });
211 | }
212 |
213 | final introductionString = prefs.getString(scheduleIntroVideo);
214 | // New anime introduction isn't that important so it is fine to fail
215 | if (introductionString != null && introductionString != "null") {
216 | this._introductory = AnimeVideo.fromJson(
217 | jsonDecode(introductionString),
218 | );
219 | }
220 | }
221 |
222 | // Load recent anime, you always need to load this
223 | await this.getRecentAnime();
224 | }
225 |
226 | void _resetList() {
227 | this._animeList = [];
228 | this._animeScheduleList = [];
229 | }
230 |
231 | Future checkGithubUpdate() async {
232 | if (!this.hasUpdate) {
233 | final parser = new GithubParser(githubRelease);
234 | Document? body = await parser.downloadHTML();
235 | this._update = parser.parseHTML(body);
236 | this.hasUpdate = true;
237 | }
238 | }
239 |
240 | Future _getAnimeList() async {
241 | final parser = new AnimeListParserV2();
242 | Document? doc = await parser.downloadHTML();
243 | // Check if it is valid
244 | if (doc != null) {
245 | _animeList = parser.parseHTML(doc);
246 | if (_animeList.length > 0)
247 | prefs.setString(animeList, jsonEncode(_animeList));
248 | }
249 | }
250 |
251 | Future _getAnimeScedule() async {
252 | final link = this.getScheduleLink();
253 | final parser = new AnimeScheduleParser(link);
254 | final body = await parser.downloadHTML();
255 | _animeScheduleList = parser.parseHTML(body);
256 | _introductory = parser.parseIntroductoryVideo(body);
257 | // Only save it if it is valid
258 | if (_animeScheduleList.length > 0)
259 | prefs.setString(animeScedule, jsonEncode(_animeScheduleList));
260 | prefs.setString(scheduleIntroVideo, jsonEncode(_introductory));
261 | }
262 |
263 | /// Load recent anime
264 | Future getRecentAnime() async {
265 | this._recentParser = new AnimeRecentParser(GlobalData.domain);
266 | final body = await this._recentParser.downloadHTML();
267 | this._recentList = this._recentParser.parseHTML(body);
268 | }
269 |
270 | /// launch wikipedia page for anime
271 | void getWikipediaLink(String? name) {
272 | // Somehow I need to encode on IOS but not on Android
273 | final link = Uri.encodeFull(
274 | 'https://zh.wikipedia.org/w/index.php?search=$name',
275 | );
276 | launch(link);
277 | }
278 |
279 | /// get a string like https://youtube.com/watch?v=xxx
280 | String getYouTubeLink(String? vid) {
281 | // you have found an easter egg maybe?
282 | return 'https://youtube.com/watch?v=' + (vid ?? 'dQw4w9WgXcQ');
283 | }
284 |
285 | /// send an email to HenryQuan
286 | void sendEmail(String? extra) {
287 | launch(
288 | 'mailto:development.henryquan@gmail.com?subject=[AnimeOne ${GlobalData.version}]&body=$extra',
289 | );
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeBasic.dart:
--------------------------------------------------------------------------------
1 | /// A basic anime class that has a name and link
2 | abstract class AnimeBasic {
3 | String? name;
4 | String? link;
5 |
6 | /// Check if name contains t
7 | bool contains(String t) {
8 | final tL = t.toLowerCase();
9 | final nL = this.name?.toLowerCase();
10 | if (nL == null) {
11 | return false;
12 | } else {
13 | return nL.contains(tL);
14 | }
15 | }
16 |
17 | AnimeBasic.fromJson(Map? json) {
18 | if (json == null) return;
19 | name = json['name'];
20 | link = json['link'];
21 | }
22 |
23 | /// Check if name is loaded and not null
24 | bool valid() {
25 | return name != null && name!.trim().length > 0;
26 | }
27 |
28 | /// Move episode number in front ([12] xxxx)
29 | String? formattedName() {
30 | if (name == null) {
31 | return null;
32 | } else if (name!.endsWith(']')) {
33 | var group = name!.split(' ');
34 | String tag = group.removeLast();
35 | String rest = group.join(' ');
36 | String last = '$tag $rest';
37 |
38 | // Double check the tag is in front now
39 | if (last.startsWith('[')) return last;
40 | return name;
41 | } else {
42 | return name;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeEntry.dart:
--------------------------------------------------------------------------------
1 | import 'dart:developer';
2 |
3 | import 'package:animeone/core/GlobalData.dart';
4 | import 'package:animeone/core/anime/AnimeBasic.dart';
5 | import 'package:animeone/core/anime/AnimeVideo.dart';
6 | import 'package:html/dom.dart';
7 |
8 | /// This class saves anime name, anime page link, anime video link, post date, all episodes and next episode
9 | class AnimeEntry extends AnimeBasic {
10 | String? postDate;
11 | String? allEpisodes;
12 | String? nextEpisode;
13 | AnimeVideo? videoLink;
14 |
15 | AnimeEntry(Element e) : super.fromJson(null) {
16 | try {
17 | // There are rare occasions where you need to enter password
18 | Node title = e.getElementsByClassName('entry-title')[0].nodes[0];
19 | this.name = title.text;
20 | this.link = title.attributes['href'];
21 |
22 | Node post = e.getElementsByClassName('entry-date')[0];
23 | this.postDate = post.text;
24 |
25 | // Get iframe instead
26 | var iframe = e.getElementsByTagName('iframe');
27 | if (iframe.length > 0) {
28 | // There are exceptions where iframe is not used
29 | Element video = iframe[0];
30 | this.videoLink = new AnimeVideo(video.attributes['src']);
31 | } else {
32 | // Get loadView button
33 | var loadBtn = e.getElementsByClassName('loadvideo');
34 | if (loadBtn.length > 0) {
35 | Element btn = loadBtn[0];
36 | this.videoLink = new AnimeVideo(btn.attributes['data-src']);
37 | } else {
38 | // See if it needs passwords
39 | var password = e.getElementsByClassName('acpwd-container');
40 | if (password.length > 0) {
41 | // Use need to enter some password
42 | } else {
43 | // Check if it is a YouTube preview
44 | final youtube = e.getElementsByClassName('youtubePlayer');
45 | if (youtube.length > 0) {
46 | final element = youtube[0];
47 | final link = GlobalData().getYouTubeLink(
48 | element.attributes['data-vid'],
49 | );
50 | this.videoLink = new AnimeVideo(link);
51 | } else {
52 | // find the video tag and get data-apireq from it
53 | final videoTags = e.getElementsByTagName('video');
54 | if (videoTags.length > 0) {
55 | final element = videoTags[0];
56 | final link = element.attributes['data-apireq'];
57 | this.videoLink = new AnimeVideo(link);
58 | } else {
59 | // this is probably something new again
60 | }
61 | }
62 | }
63 | }
64 | }
65 |
66 | // Episode links
67 | e.getElementsByTagName('a').forEach((n) {
68 | final href = n.attributes['href'];
69 | if (href != null) {
70 | final episodeLink = GlobalData.domain + href;
71 | if (n.text.contains('全集')) {
72 | // Get all episode link
73 | this.allEpisodes = episodeLink;
74 | } else if (n.text.contains('下一集')) {
75 | // Get next episode link
76 | this.nextEpisode = episodeLink;
77 | }
78 | }
79 | });
80 | } catch (e) {
81 | throw new Exception('AnimeEntry - Format changed\n${e.toString()}');
82 | }
83 | }
84 |
85 | /// Date + how many days ago
86 | /// - 一天前
87 | /// 一周前
88 | /// - 一個月前
89 | /// - 一年之前
90 | String getEnhancedDate() {
91 | String enhanced = '';
92 | final postDateString = this.postDate;
93 | if (postDateString != null) {
94 | final date = DateTime.parse(postDateString);
95 | int dayDiff = date.difference(DateTime.now()).inDays.abs();
96 | log(dayDiff.toString());
97 |
98 | if (dayDiff == 0) {
99 | enhanced = '今天';
100 | } else if (dayDiff == 1) {
101 | enhanced = '昨天';
102 | } else if (dayDiff < 7) {
103 | enhanced = '$dayDiff 天前';
104 | } else if (dayDiff < 28) {
105 | enhanced = '${(dayDiff / 7).round()} 周前';
106 | } else if (dayDiff < 365) {
107 | enhanced =
108 | '${(dayDiff / 30).toStringAsFixed(1)} 個月前'; // is this a good idea??
109 | } else {
110 | enhanced = '${(dayDiff / 365).toStringAsFixed(1)} 年前';
111 | }
112 |
113 | return this.postDate! + ' | $enhanced';
114 | } else {
115 | return '未知';
116 | }
117 | }
118 |
119 | /// If next episode is avaible
120 | bool hasNextEpisode() {
121 | if (nextEpisode != null) {
122 | return !nextEpisode!.endsWith('/?p=');
123 | } else {
124 | return false;
125 | }
126 | }
127 |
128 | /// In certain regions, password is needed due to copyright protection
129 | bool needPassword() {
130 | return this.videoLink == null;
131 | }
132 |
133 | AnimeVideo? getVideo() => this.videoLink;
134 | }
135 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeInfo.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:html/dom.dart';
3 |
4 | import 'AnimeBasic.dart';
5 |
6 | /// This class parses a Node and stores anime info like anime name, anime link, total episodes, year, season and subtitle group
7 | class AnimeInfo extends AnimeBasic {
8 | String? episode;
9 | String? year;
10 | String? season;
11 | String? subtitle;
12 |
13 | AnimeInfo(Node tr) : super.fromJson(null) {
14 | final list = tr.nodes;
15 | try {
16 | this.name = list[0].text;
17 | final href = list[0].nodes[0].attributes["href"];
18 | if (href != null) this.link = GlobalData.domain + href;
19 | this.episode = list[1].text;
20 | this.year = list[2].text;
21 | this.season = list[3].text;
22 | this.subtitle = list[4].text ?? "-";
23 | } catch (e) {
24 | throw new Exception('AnimeInfo - Tr has been changed\n${e.toString()}');
25 | }
26 | }
27 |
28 | @override
29 | bool contains(String t) {
30 | // emm, any better way of writing this?
31 | if (super.contains(t)) return true;
32 | if (year != null && season != null && (year! + season!).contains(t))
33 | return true;
34 | if (episode != null && episode!.contains(t)) return true;
35 | if (subtitle != null && subtitle!.contains(t)) return true;
36 |
37 | return false;
38 | }
39 |
40 | AnimeInfo.fromJson(Map json)
41 | : episode = json['episode'],
42 | year = json['year'],
43 | season = json['season'],
44 | subtitle = json['subtitle'],
45 | super.fromJson(json);
46 |
47 | AnimeInfo.fromList(List list) : super.fromJson(null) {
48 | // The ID is the link
49 | this.link = 'https://anime1.me/?cat=${list[0]}';
50 | this.name = list[1];
51 | this.episode = list[2];
52 | this.year = list[3];
53 | this.season = list[4];
54 | this.subtitle = list[5];
55 | }
56 |
57 | Map toJson() => {
58 | 'subtitle': subtitle,
59 | 'season': season,
60 | 'year': year,
61 | 'episode': episode,
62 | 'name': name,
63 | 'link': link
64 | };
65 |
66 | @override
67 | String toString() {
68 | return "Name: $name\nLink: $link\nEpisode: $episode\nYear: $year\nSeason: $season\nSubtitle: $subtitle";
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeRecent.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeBasic.dart';
2 | import 'package:html/dom.dart';
3 |
4 | /// This class parses a Node and stores anime recent (only name and link)
5 | class AnimeRecent extends AnimeBasic {
6 | AnimeRecent(Node tr) : super.fromJson(null) {
7 | final anime = tr.firstChild;
8 | try {
9 | this.name = anime?.text;
10 | this.link = anime?.attributes['href'];
11 | } catch (e) {
12 | throw new Exception('AnimeRecent - Tr has been changed\n${e.toString()}');
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeSchedule.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/core/anime/AnimeBasic.dart';
3 | import 'package:html/dom.dart';
4 |
5 | /// This class saves anime name, link and schedule (0 - 6)
6 | class AnimeSchedule extends AnimeBasic {
7 | int? weekday;
8 |
9 | AnimeSchedule(Node tr, int i) : super.fromJson(null) {
10 | weekday = i;
11 | // Fix for Sunday (anime1 puts Sunday first)
12 | if (weekday == 0) weekday = 7;
13 | weekday = weekday! - 1;
14 |
15 | // Same are empty
16 | if (tr.firstChild != null) {
17 | this.name = tr.firstChild?.text;
18 | // They haven't put the link so be careful
19 | String? link = tr.firstChild?.attributes['href'];
20 | if (link != null) {
21 | this.link = GlobalData.domain + link;
22 | }
23 | }
24 | }
25 |
26 | AnimeSchedule.fromJson(Map json)
27 | : weekday = json['weekday'],
28 | super.fromJson(json);
29 |
30 | Map toJson() => {
31 | 'weekday': weekday!,
32 | 'name': name,
33 | 'link': link,
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeSeason.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 |
3 | /// This class asks for DateTime to get a string to indicate seasonal anime
4 | class AnimeSeason {
5 | late DateTime _date;
6 | final _seasons = ['冬季', '春季', '夏季', '秋季'];
7 |
8 | AnimeSeason(DateTime date) {
9 | this._date = date;
10 | }
11 |
12 | String getLink() {
13 | return '${GlobalData.domain}$this';
14 | }
15 |
16 | /// Link for Anime page to use
17 | String getAnimeLink() {
18 | return '${GlobalData.domain}category/$this'.replaceFirst('新番', '');
19 | }
20 |
21 | /// some preset filters
22 | List getQuickFilters() {
23 | List filters = ['連載中', '劇場版', 'OVA', 'OAD'];
24 |
25 | // Add recent 4 seasons
26 | int offset = 0;
27 | for (int i = 0; i < 4; i++, offset -= 3) {
28 | // Keep updating the date
29 | var temp =
30 | this._getYearAndSeason(this._date.add(Duration(days: offset * 30)));
31 | filters.add('${temp[0]}${this._seasons[temp[1]][0]}');
32 | }
33 |
34 | return filters;
35 | }
36 |
37 | List _getYearAndSeason(DateTime dt) {
38 | int year = dt.year;
39 | int month = dt.month;
40 |
41 | int season;
42 | if (month < 4)
43 | season = 0;
44 | else if (month < 7)
45 | season = 1;
46 | else if (month < 10)
47 | season = 2;
48 | else
49 | season = 3;
50 |
51 | return [year, season];
52 | }
53 |
54 | @override
55 | String toString() {
56 | var yas = this._getYearAndSeason(this._date);
57 |
58 | return '${yas[0]}年${this._seasons[yas[1]]}新番';
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/animeone/lib/core/anime/AnimeVideo.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:url_launcher/url_launcher.dart';
4 |
5 | /// This class save the video link and randomly choose one of the cover images
6 | class AnimeVideo {
7 | String? video;
8 | int? image;
9 |
10 | // From 3 -> 9
11 | final _covers = List.generate(7, (i) => i + 3);
12 |
13 | AnimeVideo(String? video) {
14 | this.video = video;
15 | this.image = _covers[Random().nextInt(6)];
16 | }
17 |
18 | AnimeVideo.fromJson(Map json)
19 | : video = json['video'],
20 | image = json['image'];
21 |
22 | Map toJson() => {
23 | 'video': video,
24 | 'image': image,
25 | };
26 |
27 | void launchURL() {
28 | final video = this.video;
29 | if (video != null) launch(video);
30 | }
31 |
32 | bool? get hasToken {
33 | /// It is something like this
34 | /// %7B%22c%22%3A%221003%22%2C%22e%22%3A%222b%22%2C%22t%22%3A1642745961%2C%22p%22%3A0%2C%22s%22%3A%220f83a764869105145b69e51e475f5ab7%22%7D
35 | return video?.startsWith('%7B%22');
36 | }
37 |
38 | /// Check if this is a youtube link
39 | bool isYoutube() {
40 | return video != null && video!.contains('youtube');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/animeone/lib/core/interface/FullscreenPlayer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 |
3 | mixin FullscreenPlayer {
4 | void setLandscape() {
5 | // Fullscreen mode
6 | SystemChrome.setEnabledSystemUIMode(
7 | SystemUiMode.manual,
8 | // Hide the status bar
9 | overlays: [],
10 | );
11 |
12 | // Landscape only
13 | SystemChrome.setPreferredOrientations([
14 | DeviceOrientation.landscapeLeft,
15 | DeviceOrientation.landscapeRight,
16 | ]);
17 | }
18 |
19 | void resetOrientation() {
20 | // Reset UI overlay
21 | SystemChrome.setEnabledSystemUIMode(
22 | SystemUiMode.manual,
23 | overlays: SystemUiOverlay.values,
24 | );
25 |
26 | // Reset orientation
27 | SystemChrome.setPreferredOrientations([
28 | DeviceOrientation.portraitUp,
29 | ]);
30 | SystemChrome.setPreferredOrientations([
31 | DeviceOrientation.portraitUp,
32 | DeviceOrientation.portraitDown,
33 | DeviceOrientation.landscapeLeft,
34 | DeviceOrientation.landscapeRight,
35 | ]);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/animeone/lib/core/other/GithubUpdate.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:url_launcher/url_launcher.dart';
4 |
5 | /// This class has Github app version and its download link
6 | class GithubUpdate {
7 | String? version;
8 | String? link;
9 | String? whatsnew;
10 |
11 | GithubUpdate.fromJson(Map json)
12 | : version = json['version'],
13 | link = json['link'],
14 | whatsnew = json['new'];
15 |
16 | Map toJson() => {
17 | 'version': version,
18 | 'link': link,
19 | 'new': whatsnew,
20 | };
21 |
22 | /// Check if version is current and launch the link if so
23 | void checkUpdate(BuildContext context, {bool showAlertWhenNoUpdate = false}) {
24 | // Only Android devices can download new apk
25 | bool isAndroid = Theme.of(context).platform == TargetPlatform.android;
26 | String extraInfo = '';
27 | if (!isAndroid) extraInfo = '\n請重新編譯APP';
28 |
29 | if (version != GlobalData.version) {
30 | showDialog(
31 | context: context,
32 | // Prevent accidental dismiss
33 | barrierDismissible: false,
34 | builder: (BuildContext context) {
35 | // Has update now
36 | return AlertDialog(
37 | title: Text('v$version'),
38 | content: Text(whatsnew! + extraInfo),
39 | actions: [
40 | TextButton(
41 | child: Text('關閉'),
42 | onPressed: () => Navigator.of(context).pop(),
43 | ),
44 | // Render nothing for non-android devices
45 | isAndroid
46 | ? TextButton(
47 | child: Text('立即下載'),
48 | onPressed: () {
49 | launch(link!);
50 | Navigator.of(context).pop();
51 | },
52 | )
53 | : Container(),
54 | ],
55 | );
56 | },
57 | );
58 | } else if (showAlertWhenNoUpdate) {
59 | // This should only shown in settings
60 | showDialog(
61 | context: context,
62 | // Prevent accidental dismiss
63 | barrierDismissible: false,
64 | builder: (BuildContext context) {
65 | // No update
66 | return AlertDialog(
67 | title: Text('v$version'),
68 | content: Text('沒有發現更新,目前已經是最新版本'),
69 | actions: [
70 | TextButton(
71 | child: Text('關閉'),
72 | onPressed: () => Navigator.of(context).pop(),
73 | ),
74 | ],
75 | );
76 | },
77 | );
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/AnimeListParser.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeInfo.dart';
2 | import 'package:animeone/core/parser/BasicParser.dart';
3 | import 'package:html/dom.dart';
4 |
5 | /// This class parses all anime available from the site
6 | @Deprecated("This parser is no longer working and shouldn't be used")
7 | class AnimeListParser extends BasicParser {
8 | AnimeListParser(String link) : super(link);
9 |
10 | @override
11 | List parseHTML(Document? body) {
12 | List list = [];
13 |
14 | final elements = body?.getElementsByClassName("row-hover");
15 | final e = elements?.first;
16 |
17 | if (e?.hasChildNodes() ?? false) {
18 | e?.nodes.forEach((n) => list.add(new AnimeInfo(n)));
19 | }
20 |
21 | return list;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/AnimeListParserV2.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:animeone/core/anime/AnimeInfo.dart';
4 | import 'package:animeone/core/parser/BasicParser.dart';
5 | import 'package:html/dom.dart';
6 |
7 | /// This class parses all anime available from the site by requesting to
8 | class AnimeListParserV2 extends BasicParser {
9 | AnimeListParserV2() : super('https://d1zquzjgwo9yb.cloudfront.net/?_=');
10 |
11 | @override
12 | List parseHTML(Document? body) {
13 | List list = [];
14 |
15 | final text = body?.children[0].text;
16 | if (text == null) return list;
17 |
18 | final json = jsonDecode(text);
19 | // It should be a list of list
20 | if (json is List) {
21 | json.forEach((item) {
22 | // 0 means that it is 🔞
23 | if (item is List && item[0] > 0) {
24 | list.add(AnimeInfo.fromList(item));
25 | }
26 | });
27 | } else {
28 | throw Exception('Invalid json');
29 | }
30 |
31 | return list;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/AnimePageParser.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeEntry.dart';
2 | import 'package:animeone/core/parser/BasicParser.dart';
3 | import 'package:html/dom.dart';
4 |
5 | /// This class gets all episodes of an anime or just one episode
6 | /// - https://anime1.me/category/2019年春季/鬼滅之刃/page/0
7 | /// - https://anime1.me/10391
8 | ///
9 | /// The long link can up to 14 episodes and the short one only has one.
10 | /// They have a link to all episodes or next episode
11 | class AnimePageParser extends BasicParser {
12 | AnimePageParser(String link) : super(link);
13 |
14 | @override
15 | List parseHTML(Document? body) {
16 | List list = [];
17 |
18 | final elements = body?.getElementsByClassName("hentry");
19 | elements?.forEach((e) => list.add(new AnimeEntry(e)));
20 | return list;
21 | }
22 |
23 | /// Get page title to be displayed in app bar
24 | String getPageTitle(Document body) {
25 | final titles = body.getElementsByClassName('page-title');
26 | if (titles.length > 0) {
27 | return titles.first.text;
28 | } else {
29 | return '';
30 | }
31 | }
32 |
33 | /// Get full link instead of /cat for going toi next page
34 | String? getFullLink(Document body) {
35 | final link = body.getElementsByClassName('cat-links').first;
36 | return link.nodes[1].attributes['href'];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/AnimeRecentParser.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeRecent.dart';
2 | import 'package:html/dom.dart';
3 |
4 | import 'BasicParser.dart';
5 |
6 | /// This class get recent anime
7 | class AnimeRecentParser extends BasicParser {
8 | AnimeRecentParser(String link) : super(link);
9 |
10 | @override
11 | List parseHTML(Document? body) {
12 | List recent = [];
13 |
14 | final widgets = body?.getElementsByClassName('widget-area');
15 | final list = widgets?.first.getElementsByTagName('ul');
16 | list?.first.nodes.forEach((tr) {
17 | if (tr.text?.trim() != "") {
18 | recent.add(new AnimeRecent(tr));
19 | }
20 | });
21 |
22 | return recent;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/AnimeScheduleParser.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeSchedule.dart';
2 | import 'package:animeone/core/anime/AnimeVideo.dart';
3 | import 'package:animeone/core/parser/BasicParser.dart';
4 | import 'package:html/dom.dart';
5 |
6 | /// This class get anime schedule and possibly an introductory video
7 | class AnimeScheduleParser extends BasicParser {
8 | AnimeScheduleParser(String link) : super(link);
9 |
10 | @override
11 | List parseHTML(Document? body) {
12 | List schedules = [];
13 |
14 | final tables = body?.getElementsByTagName('table');
15 | final tbody = tables?.first.nodes[1];
16 | tbody?.nodes.forEach((tr) {
17 | // anime1.me is also one line (so check the length to prevent it)
18 | if (tr.nodes.length > 1) {
19 | // It is in order so use an index to indicate the date
20 | int i = 0;
21 | tr.nodes.forEach((td) {
22 | AnimeSchedule t = new AnimeSchedule(td, i++);
23 | if (t.valid()) schedules.add(t);
24 | });
25 | }
26 | });
27 |
28 | return schedules;
29 | }
30 |
31 | /// get AnimeVideo from schedule (there might be one)
32 | AnimeVideo? parseIntroductoryVideo(Document? body) {
33 | final frames = body?.getElementsByTagName('iframe');
34 | if (frames == null || frames.length == 0) {
35 | return null;
36 | } else {
37 | return new AnimeVideo(frames.first.attributes['src']);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/BasicParser.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 |
4 | import 'package:animeone/core/GlobalData.dart';
5 | import 'package:html/dom.dart';
6 | import 'package:html/parser.dart';
7 | import 'package:http/http.dart';
8 | import 'package:http/http.dart' as http;
9 |
10 | /// This is the parent of all parsers and it handles 404 not found.
11 | /// This is also the termination point of next page or back to home
12 | abstract class BasicParser {
13 | final String _link;
14 | late final String _cookie;
15 | late final String _userAgent;
16 |
17 | /// Get the link for current page
18 | String getLink() => this._link;
19 |
20 | BasicParser(this._link) {
21 | final data = GlobalData();
22 | this._cookie = data.getCookie();
23 | this._userAgent = data.getUserAgent();
24 |
25 | // this._cookie = '__cfduid=d51d3b47667b64ea1c0278ca1baec11f41583638164; _ga=GA1.2.1712035882.1586138123; _gid=GA1.2.378879752.1586138123; cf_clearance=b92f05ead855fb1ec43fdad0205f81c366c23ce0-1586138131-0-150; videopassword=0';
26 | print(this._link);
27 | }
28 |
29 | Map get _defaultHeader {
30 | return {
31 | 'cookie': _cookie,
32 | 'user-agent': _userAgent,
33 | 'referer': 'https://anime1.me/',
34 | };
35 | }
36 |
37 | /// Download HTML string from link
38 | Future downloadHTML() async {
39 | try {
40 | return handleReponse(await get());
41 | } catch (e) {
42 | print(e);
43 | return null;
44 | }
45 | }
46 |
47 | Future get({
48 | Map? headers = null,
49 | }) async {
50 | try {
51 | var link = this._link;
52 | // TODO: better solution is needed here if there is one
53 | // redirection handling
54 | if (link.contains('/?cat')) link = await _redirect();
55 |
56 | return await http
57 | .get(
58 | Uri.parse(link),
59 | headers: headers ?? _defaultHeader,
60 | )
61 | .timeout(Duration(seconds: 10));
62 | } catch (e) {
63 | // catch timeout here
64 | print(e);
65 | return null;
66 | }
67 | }
68 |
69 | Future _redirect() async {
70 | String finalLink = this._link;
71 | try {
72 | String? redirected = this._link;
73 | // WHen it is null, it means that there is no more redirect and it is the latest domain
74 | while (redirected != null) {
75 | // handle redirects manually
76 | final request = Request('GET', Uri.parse(redirected))
77 | ..followRedirects = false;
78 | final response = await Client().send(request);
79 | // get the redirect link
80 | redirected = response.headers['location'];
81 | print("Redirected to $redirected");
82 | if (redirected != null) {
83 | finalLink = redirected;
84 | }
85 | }
86 |
87 | return finalLink;
88 | } catch (e) {
89 | print(e);
90 | return finalLink;
91 | }
92 | }
93 |
94 | Future post({
95 | Map? headers = null,
96 | Object? body,
97 | Encoding? encoding,
98 | }) async {
99 | try {
100 | return await http
101 | .post(
102 | Uri.parse(this._link),
103 | headers: headers ?? _defaultHeader,
104 | body: body,
105 | encoding: encoding,
106 | )
107 | .timeout(Duration(seconds: 10));
108 | } catch (e) {
109 | // catch timeout here
110 | print(e);
111 | return null;
112 | }
113 | }
114 |
115 | Document? handleReponse(Response? response) {
116 | if (response == null) return null;
117 |
118 | if (response.statusCode == 200) {
119 | // Encoding is needed to prevent unexpected error (espcially Chinese character)
120 | final encoded = Utf8Encoder().convert(response.body);
121 | return parse(encoded);
122 | } else if (response.statusCode == 503) {
123 | // Need to get cookie
124 | GlobalData.requestCookieLink = this._link;
125 | return null;
126 | } else {
127 | // If it is 404, the status code will tell you
128 | return null;
129 | }
130 | }
131 |
132 | /// All subclasses have different implementations
133 | parseHTML(Document? body);
134 | }
135 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/GithubParser.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:animeone/core/other/GithubUpdate.dart';
4 | import 'package:animeone/core/parser/BasicParser.dart';
5 | import 'package:html/dom.dart';
6 |
7 | /// This class get anime schedule and possibly an introductory video
8 | class GithubParser extends BasicParser {
9 | GithubParser(String link) : super(link);
10 |
11 | @override
12 | GithubUpdate? parseHTML(Document? body) {
13 | final text = body?.firstChild?.text;
14 | if (text == null) return null;
15 |
16 | return GithubUpdate.fromJson(jsonDecode(text));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/animeone/lib/core/parser/VideoSourceParser.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:animeone/core/parser/BasicParser.dart';
4 | import 'package:html/dom.dart';
5 |
6 | class VideoSourceParser extends BasicParser {
7 | VideoSourceParser() : super('https://v.anime1.me/api');
8 |
9 | @override
10 | String? parseHTML(Document? body) {
11 | try {
12 | final rawJSON = body?.children[0];
13 | if (rawJSON == null) return null;
14 | final videoJSON = json.decode(rawJSON.text) as Map?;
15 | print(videoJSON);
16 | // it is now a list
17 | final videoLink = videoJSON?['s']?[0]?['src'] as String?;
18 | if (videoLink == null) return null;
19 |
20 | if (videoLink.contains('http')) return videoLink;
21 | return 'https:$videoLink';
22 | } catch (e, s) {
23 | print(s);
24 | assert(false, 'VideoSourceParser - Error parsing HTML\n$e');
25 | return null;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/animeone/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/ui/page/home.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 |
5 | /// Entry point of this app
6 | void main() {
7 | runApp(MyApp());
8 | }
9 |
10 | /// Top level component
11 | class MyApp extends StatefulWidget {
12 | @override
13 | _MyAppState createState() => _MyAppState();
14 | }
15 |
16 | class _MyAppState extends State {
17 | @override
18 | void initState() {
19 | super.initState();
20 | final window = WidgetsBinding.instance.window;
21 | window.onPlatformBrightnessChanged = () {
22 | final brightness = window.platformBrightness;
23 | // update navigation bar colour
24 | SystemChrome.setSystemUIOverlayStyle(
25 | SystemUiOverlayStyle(
26 | systemNavigationBarColor:
27 | brightness == Brightness.light ? Colors.white : Colors.black,
28 | systemNavigationBarIconBrightness: brightness == Brightness.light
29 | ? Brightness.dark
30 | : Brightness.light,
31 | ),
32 | );
33 | };
34 | }
35 |
36 | final darkTheme = ThemeData(
37 | brightness: Brightness.dark,
38 | colorScheme: ColorScheme.fromSwatch(
39 | primarySwatch: Colors.pink,
40 | brightness: Brightness.dark,
41 | ).copyWith(
42 | // set navigation tab bar tint colour
43 | secondary: Colors.pinkAccent,
44 | ),
45 | );
46 |
47 | final lightTheme = ThemeData(
48 | primarySwatch: Colors.pink,
49 | appBarTheme: AppBarTheme(
50 | systemOverlayStyle: SystemUiOverlayStyle.light,
51 | ),
52 | );
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return MaterialApp(
57 | title: 'AnimeOne',
58 | theme: lightTheme,
59 | darkTheme: darkTheme,
60 | home: HomePage(),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/AnimeButton.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeBasic.dart';
2 | import 'package:animeone/ui/page/anime.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | /// This a button that accepts AnimeSchedule or AnimeRecent
6 | class AnimeButton extends StatelessWidget {
7 | const AnimeButton({
8 | Key? key,
9 | required this.basic,
10 | this.recent,
11 | }) : super(key: key);
12 |
13 | final AnimeBasic basic;
14 | final bool? recent;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return SizedBox(
19 | height: 48,
20 | child: InkWell(
21 | onTap: () {
22 | // It might be null
23 | if (basic.link != null) {
24 | Navigator.push(
25 | context,
26 | MaterialPageRoute(
27 | builder: (context) => Anime(
28 | link: basic.link,
29 | recent: recent,
30 | ),
31 | ),
32 | );
33 | } else {
34 | showDialog(
35 | context: context,
36 | builder: (BuildContext context) {
37 | // Has update now
38 | return AlertDialog(
39 | content: Text('動畫還沒有更新第一集 >_<', textAlign: TextAlign.center),
40 | );
41 | },
42 | );
43 | }
44 | },
45 | child: Align(
46 | alignment: Alignment.centerLeft,
47 | child: Padding(
48 | padding: const EdgeInsets.only(left: 16),
49 | child: Text(
50 | basic.formattedName() ?? "賽博朋克",
51 | maxLines: 1,
52 | textAlign: TextAlign.left,
53 | style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16),
54 | ),
55 | ),
56 | ),
57 | ),
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/AnimeCoverImage.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeVideo.dart';
2 | import 'package:animeone/ui/page/video.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:url_launcher/url_launcher.dart';
5 |
6 | /// Takes an AnimeVideo object and render it to an Image
7 | class AnimeCoverImage extends StatelessWidget {
8 | AnimeCoverImage({
9 | Key? key,
10 | required this.video,
11 | }) : super(key: key);
12 |
13 | final AnimeVideo? video;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return LayoutBuilder(builder: (context, constraint) {
18 | return Tooltip(
19 | message:
20 | this.shouldEnterPassword() ? '因版權方要求,目前無法提供此内容。' : '點擊進入内置視頻播放器',
21 | child: Stack(children: [
22 | AspectRatio(
23 | aspectRatio: 16 / 9,
24 | child: ClipRRect(
25 | borderRadius: BorderRadius.circular(8),
26 | child: FittedBox(
27 | child: Image.asset('lib/assets/cover/${video?.image}.jpg'),
28 | ),
29 | ),
30 | ),
31 | Positioned.fill(
32 | child: this.renderButton(context, constraint),
33 | ),
34 | ]),
35 | );
36 | });
37 | }
38 |
39 | Widget renderButton(BuildContext context, BoxConstraints constraint) {
40 | if (this.shouldEnterPassword()) {
41 | return TextButton(
42 | child: Text(
43 | '啓動瀏覽器輸入密碼',
44 | style: TextStyle(
45 | backgroundColor: Colors.pink,
46 | color: Colors.white,
47 | fontSize: 18,
48 | ),
49 | ),
50 | onPressed: () {
51 | video?.launchURL();
52 | },
53 | );
54 | } else {
55 | return IconButton(
56 | onPressed: () {
57 | // video.launchURL();
58 | if (video?.isYoutube() ?? false) {
59 | video?.launchURL();
60 | } else {
61 | if (identical(0, 0.0)) {
62 | if (video != null && video!.video != null) launch(video!.video!);
63 | } else {
64 | Navigator.push(
65 | context,
66 | new MaterialPageRoute(
67 | builder: (context) => Video(video: this.video),
68 | ),
69 | );
70 | }
71 | }
72 | },
73 | iconSize: constraint.maxWidth / 6,
74 | icon: Icon(Icons.play_circle_outline),
75 | color: Colors.pink,
76 | );
77 | }
78 | }
79 |
80 | bool shouldEnterPassword() {
81 | return this.video == null;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/AnimeEntryCard.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeEntry.dart';
2 | import 'package:animeone/ui/component/AnimeCoverImage.dart';
3 | import 'package:animeone/ui/page/anime.dart';
4 | import 'package:animeone/ui/widgets/flat_button.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | /// Takes an AnimeEntry object and render it to a card
8 | class AnimeEntryCard extends StatelessWidget {
9 | AnimeEntryCard({
10 | Key? key,
11 | required this.entry,
12 | this.showEpisode,
13 | }) : super(key: key);
14 |
15 | final AnimeEntry entry;
16 | final bool? showEpisode;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Container(
21 | padding: EdgeInsets.all(16),
22 | child: Column(
23 | crossAxisAlignment: CrossAxisAlignment.start,
24 | children: [
25 | Padding(
26 | padding: const EdgeInsets.only(bottom: 8),
27 | child: Text(
28 | this.entry.formattedName() ?? '',
29 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
30 | maxLines: 1,
31 | ),
32 | ),
33 | Padding(
34 | padding: const EdgeInsets.only(bottom: 8),
35 | child: Text(
36 | this.entry.getEnhancedDate(),
37 | style: TextStyle(fontSize: 14, fontWeight: FontWeight.w300),
38 | ),
39 | ),
40 | AnimeCoverImage(
41 | video: this.entry.needPassword() ? null : this.entry.getVideo(),
42 | ),
43 | Padding(
44 | padding: const EdgeInsets.only(top: 4),
45 | child: Row(
46 | children: [
47 | this.renderAllEpisode(context),
48 | this.renderNextEpisode(context)
49 | ],
50 | ),
51 | ),
52 | ],
53 | ),
54 | );
55 | }
56 |
57 | /// Render all episode if exists or should be shown
58 | Widget renderAllEpisode(BuildContext context) {
59 | if (this.showEpisode == true && this.entry.allEpisodes != null) {
60 | return AnimeFlatButton(
61 | child: Text('全集連結'),
62 | onPressed: () {
63 | Navigator.pushReplacement(
64 | context,
65 | MaterialPageRoute(
66 | builder: (context) => Anime(link: this.entry.allEpisodes),
67 | ),
68 | );
69 | },
70 | );
71 | } else {
72 | return Container();
73 | }
74 | }
75 |
76 | /// Render next episode if exists or should be shown
77 | Widget renderNextEpisode(BuildContext context) {
78 | // Check if this is the last episode
79 | if (this.showEpisode == true &&
80 | this.entry.nextEpisode != null &&
81 | this.entry.hasNextEpisode()) {
82 | return AnimeFlatButton(
83 | child: Text('下一集'),
84 | onPressed: () {
85 | Navigator.pushReplacement(
86 | context,
87 | MaterialPageRoute(
88 | builder: (context) => Anime(link: this.entry.nextEpisode),
89 | ),
90 | );
91 | },
92 | );
93 | } else {
94 | return Container();
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/AnimeInfoCard.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/anime/AnimeInfo.dart';
2 | import 'package:animeone/ui/page/anime.dart';
3 | import 'package:animeone/ui/widgets/flat_button.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | /// Takes an AnimeInfo object and render it to a card
7 | class AnimeInfoCard extends StatelessWidget {
8 | AnimeInfoCard({
9 | Key? key,
10 | required this.info,
11 | required this.index,
12 | }) : super(key: key);
13 |
14 | final AnimeInfo info;
15 | final int index;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | bool isDark = Theme.of(context).brightness == Brightness.dark;
20 | final first = isDark ? Colors.grey[900] : Colors.white;
21 | final second = isDark ? Colors.grey[800] : Colors.grey[200];
22 |
23 | return Material(
24 | color: index % 2 == 0 ? first : second,
25 | child: AnimeFlatButton(
26 | onPressed: () {
27 | Navigator.push(
28 | context,
29 | MaterialPageRoute(
30 | builder: (context) => Anime(link: this.info.link),
31 | ),
32 | );
33 | },
34 | child: Column(
35 | children: [
36 | Padding(
37 | padding: const EdgeInsets.only(bottom: 4),
38 | child: Text(
39 | info.name ?? "賽博朋克",
40 | maxLines: 1,
41 | style: TextStyle(fontWeight: FontWeight.w700, fontSize: 16),
42 | ),
43 | ),
44 | Table(children: [
45 | TableRow(children: [
46 | Text(info.episode ?? "77", textAlign: TextAlign.center),
47 | // Cyperpunk?
48 | Text(
49 | (info.year ?? "2077") + (info.season ?? ""),
50 | textAlign: TextAlign.center,
51 | ),
52 | Text(
53 | info.subtitle ?? "",
54 | textAlign: TextAlign.center,
55 | maxLines: 1,
56 | ),
57 | ]),
58 | ]),
59 | ],
60 | ),
61 | ),
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/AnimeRecentTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/core/anime/AnimeRecent.dart';
3 | import 'package:animeone/ui/component/AnimeButton.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter/widgets.dart';
6 |
7 | class AnimeRecentTile extends StatelessWidget {
8 | AnimeRecentTile({
9 | Key? key,
10 | required this.recent,
11 | }) : super(key: key);
12 |
13 | final AnimeRecent recent;
14 | final GlobalData global = new GlobalData();
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return AnimeButton(
19 | basic: recent,
20 | recent: true,
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/AnimeScheduleTile.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/core/anime/AnimeSchedule.dart';
3 | import 'package:animeone/ui/component/AnimeButton.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class AnimeScheduleTile extends StatelessWidget {
7 | AnimeScheduleTile({
8 | Key? key,
9 | required this.schedule,
10 | }) : super(key: key);
11 |
12 | final AnimeSchedule schedule;
13 | final GlobalData global = new GlobalData();
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Row(
18 | children: [
19 | Expanded(
20 | child: new AnimeButton(basic: schedule),
21 | ),
22 | IconButton(
23 | tooltip: '使用維基百科搜索 動畫' + (schedule.name ?? ' ??'),
24 | icon: Icon(Icons.info_outline),
25 | onPressed: () => global.getWikipediaLink(schedule.name),
26 | )
27 | ],
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/EmailButton.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// EmailButton class
5 | class EmailButton extends StatelessWidget {
6 | EmailButton({
7 | Key? key,
8 | this.message,
9 | }) : super(key: key);
10 |
11 | final String? message;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return FractionallySizedBox(
16 | widthFactor: 0.618,
17 | child: ElevatedButton(
18 | style: ButtonStyle(
19 | shape: MaterialStateProperty.all(
20 | RoundedRectangleBorder(borderRadius: BorderRadius.circular(64)),
21 | ),
22 | ),
23 | onPressed: () {
24 | // Just to make sure the user doesn't send multiple emails
25 | showDialog(
26 | context: context,
27 | barrierDismissible: false,
28 | builder: (BuildContext context) {
29 | return AlertDialog(
30 | title: Text('關於加載失敗'),
31 | content: Text(
32 | '請先嘗試重新啓動APP,檢查問題依然存在。如果問題依然存在,請一天後再次嘗試,如果還是無法加載的話,再發送郵件。網站有的時候會檢查瀏覽器,所以導致 APP 無法使用。報告只會包括錯誤信息,請不要重複發送多個報告!',
33 | ),
34 | actions: [
35 | TextButton(
36 | child: Text('發送郵件'),
37 | onPressed: () => GlobalData().sendEmail(message),
38 | ),
39 | TextButton(
40 | child: Text('取消'),
41 | onPressed: () => Navigator.pop(context),
42 | ),
43 | ],
44 | );
45 | },
46 | );
47 | },
48 | child: Text('詳細信息'),
49 | ),
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/animeone/lib/ui/component/ErrorButton.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/AnimeOne.dart';
2 | import 'package:animeone/core/GlobalData.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | class ErrorButton extends StatelessWidget {
6 | ErrorButton({
7 | Key? key,
8 | this.msg,
9 | }) : super(key: key);
10 |
11 | final String? msg;
12 | final global = new GlobalData();
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | String finalMsg = '404 無法加載\n\n${msg ?? ''}';
17 | return Center(
18 | child: Column(
19 | mainAxisAlignment: MainAxisAlignment.center,
20 | children: [
21 | Text(finalMsg, textAlign: TextAlign.center),
22 | renderFixButton(context),
23 | ],
24 | ),
25 | );
26 | }
27 |
28 | Widget renderFixButton(BuildContext context) {
29 | // don't show this all the time
30 | if (GlobalData.requestCookieLink?.isNotEmpty ?? false) {
31 | // Get cookie
32 | return ElevatedButton(
33 | onPressed: () {
34 | final one = AnimeOne();
35 | one.bypassWebsiteCheck(context);
36 | },
37 | child: Text('啓動自動修復程序'),
38 | );
39 | } else {
40 | return Container();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/anime.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:animeone/core/GlobalData.dart';
4 | import 'package:animeone/core/anime/AnimeEntry.dart';
5 | import 'package:animeone/core/parser/AnimePageParser.dart';
6 | import 'package:animeone/ui/component/AnimeEntryCard.dart';
7 | import 'package:animeone/ui/component/ErrorButton.dart';
8 | import 'package:flutter/material.dart';
9 |
10 | /// This class handles anime page
11 | /// - One episode
12 | /// - All episode
13 | /// - Load next page if possible
14 | class Anime extends StatefulWidget {
15 | const Anime({
16 | Key? key,
17 | required this.link,
18 | this.seasonal,
19 | this.recent,
20 | }) : super(key: key);
21 |
22 | final String? link;
23 | final bool? seasonal;
24 | final bool? recent;
25 |
26 | @override
27 | _AnimeState createState() => _AnimeState();
28 | }
29 |
30 | class _AnimeState extends State {
31 | final global = new GlobalData();
32 |
33 | // Always start from page 1
34 | String? fullLink = '';
35 | bool loading = true;
36 | // paging
37 | int page = 1;
38 | bool hasMoreData = true;
39 | bool canLoadMore = true;
40 |
41 | // Catch error messages
42 | String hasError = '';
43 |
44 | String title = '加載失敗了...';
45 | late AnimePageParser parser;
46 | List entries = [];
47 | ScrollController? controller;
48 |
49 | @override
50 | void initState() {
51 | super.initState();
52 | this._getEntry();
53 | this.controller = new ScrollController()
54 | ..addListener(() => this.loadMore());
55 | }
56 |
57 | /// Get entries from link (support page)
58 | void _getEntry() {
59 | setState(() {
60 | this.canLoadMore = false;
61 | });
62 |
63 | String? rLink = widget.link;
64 | if (fullLink != null && fullLink != '') {
65 | rLink = fullLink! + '/page/${this.page}';
66 | }
67 |
68 | this.parser = new AnimePageParser(rLink!);
69 | this.parser.downloadHTML().then((d) {
70 | if (d == null) {
71 | // Stop loading more data
72 | this.hasMoreData = false;
73 |
74 | setState(() {
75 | this.canLoadMore = true;
76 | this.loading = false;
77 | });
78 | } else {
79 | // Category also contains cat so you need to make it longer
80 | if (widget.link?.contains('/?cat=') ?? false) {
81 | this.fullLink = this.parser.getFullLink(d);
82 | } else {
83 | this.fullLink = widget.link;
84 | }
85 |
86 | final newEntries = this.parser.parseHTML(d);
87 | setState(() {
88 | // Append more data
89 | this.entries.addAll(newEntries);
90 | this.title = this.parser.getPageTitle(d);
91 | this.loading = false;
92 | this.canLoadMore = true;
93 |
94 | // Don't auto play here, it can be quite annoying
95 | });
96 | }
97 | }).catchError((error) {
98 | // Something is broken
99 | setState(() {
100 | this.hasError = error.toString();
101 | });
102 | });
103 | }
104 |
105 | @override
106 | Widget build(BuildContext context) {
107 | // Always show error message first even if it is loading
108 | if (hasError != '') {
109 | return Scaffold(
110 | appBar: AppBar(title: Text('加载失败 QAQ')),
111 | body: ErrorButton(msg: this.hasError),
112 | );
113 | } else if (loading) {
114 | return Scaffold(
115 | appBar: AppBar(title: Text('努力加載中...')),
116 | body: Center(
117 | child: CircularProgressIndicator(),
118 | ),
119 | );
120 | } else {
121 | return Scaffold(
122 | appBar: AppBar(
123 | title: Text(this.title),
124 | actions: [this.renderSearch()],
125 | ),
126 | body: this.renderBody(),
127 | );
128 | }
129 | }
130 |
131 | /// Render a search icon to go to wikipedia
132 | Widget renderSearch() {
133 | if (this.title != '' &&
134 | widget.seasonal == null &&
135 | this.title != '加載失敗了...') {
136 | return IconButton(
137 | icon: Icon(Icons.search),
138 | onPressed: () => global.getWikipediaLink(this.title),
139 | tooltip: '使用維基百科搜索',
140 | );
141 | } else {
142 | return Container();
143 | }
144 | }
145 |
146 | /// Render differently with different number of elements
147 | Widget renderBody() {
148 | if (this.entries.length == 0) {
149 | return ErrorButton(msg: '沒有找到任何東西...');
150 | } else if (this.entries.length == 1) {
151 | return LayoutBuilder(
152 | builder: (BuildContext context, BoxConstraints constraints) {
153 | return Center(
154 | child: SizedBox(
155 | width: constraints.maxHeight,
156 | child: AnimeEntryCard(entry: this.entries.first, showEpisode: true),
157 | ),
158 | );
159 | });
160 | } else {
161 | return SafeArea(
162 | child: LayoutBuilder(
163 | builder: (BuildContext context, BoxConstraints constraints) {
164 | int count = max(min((constraints.maxWidth / 300).floor(), 3), 1);
165 | double imageWidth = constraints.maxWidth / count.toDouble();
166 | // Adjust offset
167 | double offset = 93;
168 | if (widget.seasonal != null) offset = 125;
169 | // Calculat ratio
170 | double ratio = imageWidth / (imageWidth / 1.777 + offset);
171 |
172 | int length = this.entries.length;
173 | return Stack(
174 | children: [
175 | GridView.builder(
176 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
177 | crossAxisCount: count,
178 | childAspectRatio: ratio,
179 | ),
180 | itemCount: length,
181 | itemBuilder: (context, index) {
182 | return AnimeEntryCard(
183 | entry: this.entries.elementAt(index),
184 | showEpisode: widget.seasonal == null ? false : true,
185 | );
186 | },
187 | controller: this.controller,
188 | ),
189 | this.loadDivider()
190 | ],
191 | );
192 | },
193 | ),
194 | );
195 | }
196 | }
197 |
198 | Widget loadDivider() {
199 | if (!this.loading && !this.canLoadMore) {
200 | return Align(
201 | alignment: Alignment.bottomCenter,
202 | child: LinearProgressIndicator(),
203 | );
204 | } else {
205 | return Container();
206 | }
207 | }
208 |
209 | /// Load more anime here
210 | void loadMore() {
211 | if ((controller?.position.extentAfter ?? 10) < 10 &&
212 | this.entries.length % 14 == 0 &&
213 | this.hasMoreData) {
214 | if (canLoadMore) {
215 | this.page += 1;
216 | this._getEntry();
217 | }
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/home.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/AnimeOne.dart';
2 | import 'package:animeone/core/GlobalData.dart';
3 | import 'package:animeone/ui/component/EmailButton.dart';
4 | import 'package:animeone/ui/page/latest.dart';
5 | import 'package:animeone/ui/page/list.dart';
6 | import 'package:animeone/ui/page/schedule.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:url_launcher/url_launcher.dart';
9 |
10 | class HomePage extends StatefulWidget {
11 | HomePage({Key? key}) : super(key: key);
12 |
13 | @override
14 | _HomePageState createState() => _HomePageState();
15 | }
16 |
17 | class _HomePageState extends State with TickerProviderStateMixin {
18 | int selectedIndex = 0;
19 | bool loading = true;
20 | String error = '';
21 |
22 | final global = GlobalData();
23 |
24 | @override
25 | void initState() {
26 | super.initState();
27 |
28 | this._loadData();
29 | }
30 |
31 | void _loadData() {
32 | // Reset everything
33 | setState(() {
34 | loading = true;
35 | error = '';
36 | });
37 |
38 | // Init if not and check for update
39 | this.global.init().then((_) {
40 | final update = global.getGithubUpdate();
41 | if (update != null) {
42 | update.checkUpdate(context);
43 | }
44 |
45 | if (global.showShowAgeAlert()) {
46 | // Show the alert
47 | showDialog(
48 | context: context,
49 | // Prevent accidental dismiss
50 | barrierDismissible: false,
51 | builder: (BuildContext context) {
52 | // No update
53 | return AlertDialog(
54 | title: Text('關於年齡限制'),
55 | content: Text(
56 | '最近因爲某異世界 xxx 評鑑指南的播出,雖然沒有官方的分級審核,但還是決定為 AnimeOne 增加年齡限制。本 App 至少需要 15 歲(建議18嵗)才可以使用本 App,如果你不到 15 嵗請立即刪除本 App。'),
57 | actions: [
58 | TextButton(
59 | child: Text('好的'),
60 | onPressed: () {
61 | Navigator.of(context).pop();
62 | // Don't show this again
63 | global.updateAgeAlert();
64 | },
65 | ),
66 | ],
67 | );
68 | },
69 | );
70 | }
71 |
72 | setState(() {
73 | loading = false;
74 | });
75 | }).catchError((e) {
76 | setState(() {
77 | error = e.toString();
78 | });
79 | });
80 | }
81 |
82 | @override
83 | Widget build(BuildContext context) {
84 | return Scaffold(
85 | body: this.renderBody(),
86 | bottomNavigationBar: BottomNavigationBar(
87 | items: const [
88 | BottomNavigationBarItem(
89 | icon: Icon(Icons.new_releases),
90 | label: '最新',
91 | ),
92 | BottomNavigationBarItem(
93 | icon: Icon(Icons.list),
94 | label: '動畫列表',
95 | ),
96 | BottomNavigationBarItem(
97 | icon: Icon(Icons.calendar_today),
98 | label: '時間表',
99 | ),
100 | ],
101 | currentIndex: selectedIndex,
102 | onTap: onItemTapped,
103 | ),
104 | );
105 | }
106 |
107 | /// Loading or index stacked
108 | Widget renderBody() {
109 | if (this.error != '') {
110 | return Stack(
111 | children: [
112 | Center(
113 | child: Column(
114 | mainAxisAlignment: MainAxisAlignment.center,
115 | children: [
116 | Text(
117 | '無法加載數據 :(',
118 | textAlign: TextAlign.center,
119 | style: Theme.of(context).textTheme.headline5,
120 | ),
121 | Text(
122 | '請稍後重試,如果問題依然存在,請聯係開發者\n(也許是服務器的問題 也有可能是 APP 的問題)',
123 | textAlign: TextAlign.center,
124 | style: Theme.of(context).textTheme.caption,
125 | ),
126 | SizedBox.fromSize(size: Size.fromHeight(24)),
127 | // ErrorButton(),
128 | Text(
129 | '錯誤消息!',
130 | textAlign: TextAlign.center,
131 | style: Theme.of(context).textTheme.headline5,
132 | ),
133 | Padding(
134 | padding: const EdgeInsets.all(8.0),
135 | child: Text(
136 | '$error',
137 | textAlign: TextAlign.center,
138 | style: Theme.of(context).textTheme.caption,
139 | ),
140 | ),
141 | SizedBox.fromSize(size: Size.fromHeight(24)),
142 | Text(
143 | '現在怎麽辦?',
144 | textAlign: TextAlign.center,
145 | style: Theme.of(context).textTheme.headline5,
146 | ),
147 | TextButton(
148 | child: Text('使用瀏覽器打開 anime1.me'),
149 | onPressed: () => launch(GlobalData.domain),
150 | ),
151 | Text(
152 | '或者',
153 | style: Theme.of(context).textTheme.caption,
154 | ),
155 | TextButton(
156 | child: Text('檢查 APP 是否有更新'),
157 | onPressed: () {
158 | GlobalData().checkGithubUpdate().then((_) {
159 | GlobalData()
160 | .getGithubUpdate()
161 | ?.checkUpdate(context, showAlertWhenNoUpdate: true);
162 | });
163 | },
164 | ),
165 | Text(
166 | '或者',
167 | style: Theme.of(context).textTheme.caption,
168 | ),
169 | TextButton(
170 | child: Text(
171 | '嘗試修復問題 (Beta)',
172 | style: TextStyle(fontWeight: FontWeight.bold),
173 | ),
174 | onPressed: () {
175 | if ((GlobalData.requestCookieLink?.length ?? 0) > 0) {
176 | final channel = AnimeOne();
177 | channel.bypassWebsiteCheck(context);
178 | } else {
179 | // This should be a request error
180 | showDialog(
181 | context: context,
182 | builder: (c) => AlertDialog(
183 | title: Text('沒有發現任何問題'),
184 | content: Text(
185 | '應該是網絡問題,請嘗試 【使用瀏覽器打開 anime1.me 】之後在刷新一下這個界面。如果問題依然存在,請查看詳細信息。',
186 | ),
187 | actions: [
188 | TextButton(
189 | onPressed: () => Navigator.pop(context),
190 | child: Text('好的'),
191 | ),
192 | ],
193 | ),
194 | );
195 | }
196 | },
197 | ),
198 | ],
199 | ),
200 | ),
201 | Positioned(
202 | right: 16,
203 | top: 36,
204 | child: Tooltip(
205 | message: '重新加載數據',
206 | child: IconButton(
207 | icon: Icon(Icons.refresh),
208 | onPressed: () => this._loadData(),
209 | ),
210 | ),
211 | ),
212 | Align(
213 | alignment: Alignment.bottomCenter,
214 | child: EmailButton(message: error),
215 | )
216 | ],
217 | );
218 | } else if (this.loading) {
219 | return Center(child: CircularProgressIndicator());
220 | } else {
221 | return IndexedStack(
222 | index: selectedIndex,
223 | children: [
224 | Latest(),
225 | AnimeList(),
226 | Schedule(),
227 | ],
228 | );
229 | }
230 | }
231 |
232 | /// Switch tabs
233 | void onItemTapped(int index) {
234 | setState(() {
235 | selectedIndex = index;
236 | });
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/latest.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/core/anime/AnimeRecent.dart';
3 | import 'package:animeone/ui/component/AnimeRecentTile.dart';
4 | import 'package:animeone/ui/component/ErrorButton.dart';
5 | import 'package:animeone/ui/page/support.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | class Latest extends StatefulWidget {
9 | const Latest({Key? key}) : super(key: key);
10 |
11 | @override
12 | _LatestState createState() => _LatestState();
13 | }
14 |
15 | class _LatestState extends State {
16 | bool loading = false;
17 | List list = [];
18 | final global = new GlobalData();
19 |
20 | @override
21 | void initState() {
22 | super.initState();
23 | setState(() {
24 | this.list = global.getRecentList();
25 | });
26 | }
27 |
28 | /// Load or refresh latest anime
29 | void loadRecentAnime() {
30 | // Reset to loading
31 | setState(() {
32 | this.loading = true;
33 | });
34 |
35 | global.getRecentAnime().then((d) {
36 | setState(() {
37 | this.list = global.getRecentList();
38 | this.loading = false;
39 | });
40 | });
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Scaffold(
46 | appBar: AppBar(
47 | title: Text('最新動畫'),
48 | actions: [
49 | IconButton(
50 | icon: Icon(Icons.refresh),
51 | tooltip: '刷新最新動畫',
52 | onPressed: () => this.loadRecentAnime(),
53 | )
54 | ],
55 | leading: IconButton(
56 | icon: Icon(Icons.favorite),
57 | tooltip: '支持發開',
58 | onPressed: () {
59 | // Push to support page
60 | Navigator.push(
61 | context,
62 | MaterialPageRoute(
63 | builder: (context) => Support(),
64 | ),
65 | );
66 | },
67 | ),
68 | ),
69 | body: Center(
70 | child: this.renderBody(),
71 | ),
72 | );
73 | }
74 |
75 | Widget renderBody() {
76 | if (loading) {
77 | return CircularProgressIndicator();
78 | } else if (list.length == 0) {
79 | // If somehow we cannot get recent anime
80 | return ErrorButton();
81 | } else {
82 | return SafeArea(
83 | child: ListView.builder(
84 | itemCount: list.length,
85 | itemBuilder: (context, index) {
86 | return AnimeRecentTile(recent: list.elementAt(index));
87 | },
88 | ),
89 | );
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/list.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/core/anime/AnimeInfo.dart';
3 | import 'package:animeone/ui/component/AnimeInfoCard.dart';
4 | import 'package:animeone/ui/page/settings.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class AnimeList extends StatefulWidget {
8 | AnimeList({Key? key}) : super(key: key);
9 |
10 | @override
11 | _AnimeListState createState() => _AnimeListState();
12 | }
13 |
14 | class _AnimeListState extends State {
15 | static GlobalData global = new GlobalData();
16 | List list = [];
17 | final all = global.getAnimeList();
18 |
19 | final quickFilters = global.getQuickFilters();
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Scaffold(
24 | appBar: AppBar(
25 | title: Row(
26 | children: [
27 | Padding(
28 | padding: const EdgeInsets.only(right: 8),
29 | child: Icon(Icons.search, size: 32),
30 | ),
31 | Expanded(
32 | child: TextField(
33 | style: TextStyle(color: Colors.white, fontSize: 20),
34 | decoration: InputDecoration.collapsed(
35 | hintText: '快速搜尋',
36 | hintStyle: TextStyle(color: Colors.white, fontSize: 20),
37 | ),
38 | autocorrect: false,
39 | autofocus: false,
40 | onChanged: (t) => this._filterList(t),
41 | ),
42 | )
43 | ],
44 | ),
45 | actions: [
46 | Ink.image(
47 | image: AssetImage('lib/assets/icon/logo.png'),
48 | width: 64,
49 | height: 64,
50 | child: Tooltip(
51 | message: '關於AnimeOne',
52 | child: InkWell(
53 | onTap: () {
54 | // Go to information page
55 | Navigator.push(
56 | context,
57 | new MaterialPageRoute(
58 | builder: (context) => Settings(),
59 | ),
60 | );
61 | },
62 | child: null,
63 | ),
64 | ),
65 | ),
66 | ],
67 | ),
68 | body: Column(children: [
69 | SizedBox.fromSize(
70 | size: Size.fromHeight(48),
71 | child: this.renderQuickFilter(),
72 | ),
73 | Expanded(
74 | child: this.renderBody(),
75 | ),
76 | ]),
77 | );
78 | }
79 |
80 | @override
81 | void initState() {
82 | super.initState();
83 | this._resetList();
84 | }
85 |
86 | /// render a list of quick filter
87 | Widget renderQuickFilter() {
88 | return SafeArea(
89 | child: Row(
90 | children: [
91 | Expanded(
92 | child: ListView.builder(
93 | scrollDirection: Axis.horizontal,
94 | itemCount: this.quickFilters.length,
95 | itemBuilder: (context, index) {
96 | // Get current filter
97 | final filter = this.quickFilters[index];
98 | return Padding(
99 | padding: const EdgeInsets.only(top: 8, bottom: 8, left: 8),
100 | child: Tooltip(
101 | message: '搜索 $filter 動畫',
102 | child: ActionChip(
103 | label: Text(filter),
104 | onPressed: () => this._filterList(filter),
105 | ),
106 | ),
107 | );
108 | },
109 | ),
110 | ),
111 | Tooltip(
112 | message: '重設整個列表',
113 | child: IconButton(
114 | icon: Icon(Icons.close),
115 | onPressed: () => this._resetList(),
116 | ),
117 | )
118 | ],
119 | ),
120 | );
121 | }
122 |
123 | /// render body and deal with 0 result
124 | Widget renderBody() {
125 | if (this.list.length == 0) {
126 | return Center(
127 | child: Text('找不到任何東西 (´;ω;`)'),
128 | );
129 | } else {
130 | return ListView.builder(
131 | itemCount: this.list.length,
132 | itemBuilder: (context, index) {
133 | return AnimeInfoCard(info: this.list[index], index: index);
134 | },
135 | );
136 | }
137 | }
138 |
139 | /// Filter list by string
140 | void _filterList(String t) {
141 | // At least two characters
142 | if (t == '')
143 | this._resetList();
144 | else if (t.length > 0) {
145 | setState(() {
146 | this.list = this.all.where((e) {
147 | return e.contains(t);
148 | }).toList();
149 | });
150 | }
151 | }
152 |
153 | /// Reset list to only 100 items
154 | void _resetList() {
155 | setState(() {
156 | this.list = this.all;
157 | });
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/schedule.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/core/anime/AnimeSchedule.dart';
3 | import 'package:animeone/core/anime/AnimeVideo.dart';
4 | import 'package:animeone/ui/component/AnimeScheduleTile.dart';
5 | import 'package:animeone/ui/page/anime.dart';
6 | import 'package:animeone/ui/page/video.dart';
7 | import 'package:flutter/material.dart';
8 |
9 | class Schedule extends StatefulWidget {
10 | Schedule({Key? key}) : super(key: key);
11 |
12 | @override
13 | _ScheduleState createState() => _ScheduleState();
14 | }
15 |
16 | class _ScheduleState extends State
17 | with SingleTickerProviderStateMixin {
18 | final global = new GlobalData();
19 | String? link;
20 | AnimeVideo? video;
21 | late List schedules;
22 |
23 | TabController? controller;
24 | final List tabs = [
25 | Tab(text: '一'),
26 | Tab(text: '二'),
27 | Tab(text: '三'),
28 | Tab(text: '四'),
29 | Tab(text: '五'),
30 | Tab(text: '六'),
31 | Tab(text: '日'),
32 | ];
33 |
34 | @override
35 | void initState() {
36 | super.initState();
37 |
38 | int weekday = DateTime.now().weekday - 1;
39 | this.controller =
40 | TabController(vsync: this, length: tabs.length, initialIndex: weekday);
41 |
42 | setState(() {
43 | this.schedules = this.global.getScheduleList();
44 | this.video = this.global.getIntroVideo();
45 | });
46 | }
47 |
48 | @override
49 | void dispose() {
50 | this.controller?.dispose();
51 | super.dispose();
52 | }
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return Scaffold(
57 | appBar: AppBar(
58 | bottom: TabBar(
59 | controller: controller,
60 | tabs: tabs,
61 | indicatorColor: Theme.of(context).colorScheme.primary,
62 | ),
63 | title: Padding(
64 | padding: const EdgeInsets.only(top: 8, bottom: 8),
65 | child: FractionallySizedBox(
66 | widthFactor: 0.7,
67 | child: MaterialButton(
68 | color: Colors.white,
69 | onPressed: () {
70 | Navigator.push(
71 | context,
72 | MaterialPageRoute(
73 | builder: (context) =>
74 | Anime(link: global.getSeasonLink(), seasonal: true),
75 | ),
76 | );
77 | },
78 | child: Text(
79 | global.getSeasonName(),
80 | style: TextStyle(
81 | fontWeight: FontWeight.w600,
82 | fontSize: 16,
83 | color: Colors.black87,
84 | ),
85 | ),
86 | ),
87 | ),
88 | ),
89 | actions: [
90 | IconButton(
91 | icon: Icon(Icons.play_circle_outline),
92 | tooltip: '新番介紹視頻',
93 | onPressed: () {
94 | if (this.video != null) {
95 | // this.video.launchURL();
96 | Navigator.push(
97 | context,
98 | MaterialPageRoute(
99 | builder: (context) => Video(video: this.video),
100 | ),
101 | );
102 | } else {
103 | showDialog(
104 | context: context,
105 | builder: (context) {
106 | return AlertDialog(
107 | title: Text('AnimeOne'),
108 | content: Text('沒有發現介紹視頻'),
109 | actions: [
110 | TextButton(
111 | child: Text('這樣啊'),
112 | onPressed: () {
113 | Navigator.pop(context);
114 | },
115 | )
116 | ],
117 | );
118 | },
119 | );
120 | }
121 | },
122 | )
123 | ]),
124 | body: this.renderBody(),
125 | );
126 | }
127 |
128 | /// Render body depending on whether there are data
129 | Widget renderBody() {
130 | if (this.schedules.length > 0) {
131 | return TabBarView(
132 | controller: controller,
133 | children: this.renderSchedule(),
134 | );
135 | } else {
136 | return Center(
137 | child: Text('數據還沒有更新 (´;ω;`)'),
138 | );
139 | }
140 | }
141 |
142 | /// Render schedule to different days
143 | List renderSchedule() {
144 | List children = [];
145 | for (int i = 0; i < this.tabs.length; i++) {
146 | final list = this.schedules.where((s) => s.weekday == i);
147 | children.add(SafeArea(
148 | child: ListView.builder(
149 | itemCount: list.length,
150 | itemBuilder: (c, i) {
151 | final item = list.elementAt(i);
152 | return AnimeScheduleTile(schedule: item);
153 | },
154 | ),
155 | ));
156 | }
157 |
158 | return children;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/settings.dart:
--------------------------------------------------------------------------------
1 | import 'package:animeone/core/GlobalData.dart';
2 | import 'package:animeone/ui/page/support.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:share/share.dart';
5 | import 'package:url_launcher/url_launcher.dart';
6 |
7 | class Settings extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | return Scaffold(
11 | appBar: AppBar(
12 | title: Text('關於AnimeOne'),
13 | ),
14 | body: Column(
15 | children: [
16 | ListTile(
17 | onTap: () {
18 | Navigator.push(
19 | context,
20 | MaterialPageRoute(builder: (context) => Support()),
21 | );
22 | },
23 | title: Text('支持開發'),
24 | subtitle: Text('特別喜歡本APP的話,可以支持一下~~'),
25 | trailing: Icon(Icons.favorite, color: Colors.red),
26 | ),
27 | Divider(),
28 | Expanded(
29 | child: ListView(
30 | children: [
31 | ListTile(
32 | onTap: () => launch('https://61.uy/d'),
33 | title: Text('官方Discord伺服器'),
34 | subtitle: Text('https://61.uy/d'),
35 | ),
36 | ListTile(
37 | onTap: () {
38 | launch('https://anime1.me/%e9%97%9c%e6%96%bc');
39 | },
40 | title: Text('官方網站 - 關於'),
41 | subtitle: Text('官方網站的聯繫方式和捐款'),
42 | ),
43 | Divider(),
44 | ListTile(
45 | onTap: () {
46 | launch('https://github.com/HenryQuan/AnimeOne');
47 | },
48 | title: Text('軟件源代碼'),
49 | subtitle: Text('源代碼在GitHub上開放,歡迎Pull Request'),
50 | ),
51 | ListTile(
52 | onTap: () {
53 | launch(
54 | 'https://github.com/HenryQuan/AnimeOne/blob/master/README.md#%E9%9A%B1%E7%A7%81%E6%A2%9D%E6%AC%BE',
55 | );
56 | },
57 | title: Text('隱私條款'),
58 | subtitle: Text('AnimeOne不會收集用戶的任何數據'),
59 | ),
60 | ListTile(
61 | title: Text('開源許可證'),
62 | subtitle: Text('查看所有的開源許可證'),
63 | onTap: () {
64 | Navigator.push(
65 | context,
66 | MaterialPageRoute(
67 | builder: (BuildContext context) => LicensePage(
68 | applicationName: 'AnimeOne',
69 | applicationVersion: GlobalData.version,
70 | applicationLegalese: '開源許可證',
71 | ),
72 | ),
73 | );
74 | },
75 | ),
76 | Divider(),
77 | ListTile(
78 | onTap: () {
79 | launch(GlobalData.eminaOne);
80 | },
81 | title: Text('下載 Emina One '),
82 | subtitle: Text('splitline 製作的 anime1 app'),
83 | ),
84 | ListTile(
85 | onTap: () {
86 | launch(GlobalData.animeGo);
87 | },
88 | title: Text('下載 AnimeGo'),
89 | subtitle: Text('非官方 gogoanime app (還在開發中)'),
90 | ),
91 | Divider(),
92 | ListTile(
93 | onTap: () {
94 | GlobalData().sendEmail('');
95 | },
96 | title: Text('電子郵件'),
97 | subtitle: Text('聯係本軟件的開發者'),
98 | ),
99 | ListTile(
100 | onTap: () {
101 | Share.share(GlobalData.latestRelease);
102 | },
103 | title: Text('分享軟件'),
104 | subtitle: Text('喜歡本APP的話,可以分享給朋友們'),
105 | ),
106 | ListTile(
107 | title: Text('軟件更新'),
108 | subtitle: Text(GlobalData.version),
109 | onTap: () {
110 | GlobalData().checkGithubUpdate().then((_) {
111 | GlobalData()
112 | .getGithubUpdate()
113 | ?.checkUpdate(context, showAlertWhenNoUpdate: true);
114 | });
115 | },
116 | ),
117 | ],
118 | ),
119 | ),
120 | ],
121 | ),
122 | );
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/support.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:url_launcher/url_launcher.dart';
3 |
4 | /// Support class
5 | class Support extends StatelessWidget {
6 | Support({Key? key}) : super(key: key);
7 |
8 | @override
9 | Widget build(BuildContext context) {
10 | return Scaffold(
11 | appBar: AppBar(
12 | title: Text('支持AnimeOne'),
13 | ),
14 | body: ListView(
15 | children: [
16 | ListTile(
17 | title: Text('PayPal'),
18 | subtitle: Text('短期或一次性'),
19 | onTap: () => launch('https://www.paypal.me/yihengquan'),
20 | ),
21 | ListTile(
22 | title: Text('Patreon'),
23 | subtitle: Text('長期且穩定'),
24 | onTap: () => launch('https://patreon.com/HenryQuan'),
25 | ),
26 | Padding(
27 | padding: const EdgeInsets.all(16.0),
28 | child: Text('非常感謝您的支持!'),
29 | )
30 | ],
31 | ),
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/animeone/lib/ui/page/video.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:animeone/core/anime/AnimeVideo.dart';
4 | import 'package:animeone/core/interface/FullscreenPlayer.dart';
5 | import 'package:animeone/core/parser/VideoSourceParser.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:webview_flutter/webview_flutter.dart';
8 |
9 | class Video extends StatefulWidget {
10 | final AnimeVideo? video;
11 | Video({Key? key, required this.video}) : super(key: key);
12 |
13 | @override
14 | _VideoState createState() => _VideoState();
15 | }
16 |
17 | class _VideoState extends State