├── .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 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
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 | [Get it on F-Droid](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