├── .assets ├── demo.mp4 └── test.txt ├── .github └── workflows │ └── android-release.yml ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── ani_app │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── lib ├── constants.dart ├── main.dart ├── scraper.dart ├── types.dart ├── utils.dart └── widgets.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.assets/demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdrokz/ani_mobile/33b34ef73c2443baee4c96720e0414b5cb01cd65/.assets/demo.mp4 -------------------------------------------------------------------------------- /.assets/test.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /.github/workflows/android-release.yml: -------------------------------------------------------------------------------- 1 | name: Ani Mobile Android Release 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | version: 9 | name: Create version number 10 | runs-on: macos-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Fetch all history for all tags and branches 14 | run: | 15 | git fetch --prune --depth=10000 16 | - name: Install GitVersion 17 | uses: gittools/actions/gitversion/setup@v0.9.6 18 | with: 19 | versionSpec: '5.x' 20 | - name: Use GitVersion 21 | id: gitversion 22 | uses: gittools/actions/gitversion/execute@v0.9.6 23 | - name: Create version.txt with nuGetVersion 24 | run: echo ${{ steps.gitversion.outputs.nuGetVersion }} > version.txt 25 | - name: Upload version.txt 26 | uses: actions/upload-artifact@v2 27 | with: 28 | name: gitversion 29 | path: version.txt 30 | 31 | build: 32 | name: Build APK and Create release 33 | needs: [ version ] 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v1 37 | - uses: actions/setup-java@v1 38 | with: 39 | java-version: '12.x' 40 | - uses: subosito/flutter-action@v1 41 | with: 42 | flutter-version: '2.10.1' 43 | - name: Get version.txt 44 | uses: actions/download-artifact@v2 45 | with: 46 | name: gitversion 47 | - name: Read version 48 | id: version 49 | uses: juliangruber/read-file-action@v1 50 | with: 51 | path: version.txt 52 | - run: flutter pub get 53 | # - run: flutter test 54 | - run: flutter build apk --release --split-per-abi 55 | - run: flutter build appbundle 56 | - name: Create a Release in GitHub 57 | uses: ncipollo/release-action@v1 58 | with: 59 | artifacts: "build/app/outputs/apk/release/*.apk,build/app/outputs/bundle/release/app-release.aab" 60 | token: ${{ secrets.GH_TOKEN }} 61 | tag: ${{ steps.version.outputs.content }} 62 | commit: ${{ github.sha }} 63 | -------------------------------------------------------------------------------- /.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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.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: 7e9793dee1b85a243edd0e06cb1658e98b077561 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ani_app 2 | 3 | Based on the CLI tool for watching anime [ani-cli](https://github.com/pystardust/ani-cli) 4 | 5 |

6 | Showcase 7 |

8 | 9 |
10 |
12 | 13 | # Installation 14 | 15 | Check the [releases](https://github.com/mdrokz/ani_mobile/releases) page for latest APK 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 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 flutter.compileSdkVersion 30 | 31 | packagingOptions { 32 | // Fixes duplicate libraries build issue, 33 | // when your project uses more than one plugin that depend on C++ libs. 34 | pickFirst 'lib/**/libc++_shared.so' 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | 42 | kotlinOptions { 43 | jvmTarget = '1.8' 44 | } 45 | 46 | sourceSets { 47 | main.java.srcDirs += 'src/main/kotlin' 48 | } 49 | 50 | defaultConfig { 51 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 52 | applicationId "com.example.ani_app" 53 | minSdkVersion 20 54 | targetSdkVersion flutter.targetSdkVersion 55 | versionCode flutterVersionCode.toInteger() 56 | versionName flutterVersionName 57 | } 58 | 59 | buildTypes { 60 | release { 61 | // TODO: Add your own signing config for the release build. 62 | // Signing with the debug keys for now, so `flutter run --release` works. 63 | useProguard true 64 | proguardFiles getDefaultProguardFile( 65 | 'proguard-android-optimize.txt'), 66 | 'proguard-rules.pro' 67 | signingConfig signingConfigs.debug 68 | } 69 | } 70 | } 71 | 72 | flutter { 73 | source '../..' 74 | } 75 | 76 | dependencies { 77 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 78 | } 79 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class org.videolan.libvlc.** { *; } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 22 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/ani_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.ani_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdrokz/ani_mobile/33b34ef73c2443baee4c96720e0414b5cb01cd65/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdrokz/ani_mobile/33b34ef73c2443baee4c96720e0414b5cb01cd65/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdrokz/ani_mobile/33b34ef73c2443baee4c96720e0414b5cb01cd65/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdrokz/ani_mobile/33b34ef73c2443baee4c96720e0414b5cb01cd65/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdrokz/ani_mobile/33b34ef73c2443baee4c96720e0414b5cb01cd65/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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-6.7-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | library ani_app.globals; 2 | 3 | 4 | // HTTP 5 | String baseUrl = "https://goload.pro"; 6 | String decryptionUrl = "https://goload.pro/encrypt-ajax.php"; 7 | String userAgent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2919.83 Safari/537.36"; 8 | 9 | 10 | // HTML selectors 11 | String secondKeyPath = "div[class*='videocontent-']"; 12 | String keyPath = "body[class^='container-']"; 13 | String ivPath = "div[class*='container-']"; 14 | String newSecretValuePath = "body[class*='container-']"; 15 | String secretValuePath = 'script[data-name="episode"]'; 16 | 17 | List settings = [ 18 | "", 19 | "History", 20 | "Favourites" 21 | ]; -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_vlc_player/flutter_vlc_player.dart'; 7 | import 'package:chewie/chewie.dart'; 8 | 9 | import 'scraper.dart' as scraper; 10 | 11 | import 'widgets.dart'; 12 | 13 | void main() { 14 | runApp(const MyApp()); 15 | } 16 | 17 | class MyApp extends StatelessWidget { 18 | const MyApp({Key? key}) : super(key: key); 19 | 20 | // This widget is the root of your application. 21 | @override 22 | Widget build(BuildContext context) { 23 | return MaterialApp( 24 | title: 'Ani Mobile', 25 | theme: ThemeData( 26 | // This is the theme of your application. 27 | // 28 | // Try running your application with "flutter run". You'll see the 29 | // application has a blue toolbar. Then, without quitting the app, try 30 | // changing the primarySwatch below to Colors.green and then invoke 31 | // "hot reload" (press "r" in the console where you ran "flutter run", 32 | // or simply save your changes to "hot reload" in a Flutter IDE). 33 | // Notice that the counter didn't reset back to zero; the application 34 | // is not restarted. 35 | primarySwatch: Colors.blue, 36 | ), 37 | home: const MyHomePage(title: 'Ani Mobile'), 38 | ); 39 | } 40 | } 41 | 42 | class Episode extends StatefulWidget { 43 | const Episode({Key? key, required this.streamLink}) : super(key: key); 44 | 45 | final String streamLink; 46 | 47 | @override 48 | State createState() => EpisodePage(); 49 | } 50 | 51 | class EpisodePage extends State { 52 | late VlcPlayerController videoPlayerController; 53 | late ChewieController chewieController; 54 | 55 | @override 56 | void initState() { 57 | setState(() { 58 | videoPlayerController = VlcPlayerController.network( 59 | widget.streamLink, 60 | hwAcc: HwAcc.full, 61 | autoPlay: true, 62 | autoInitialize: true, 63 | options: VlcPlayerOptions(), 64 | ); 65 | chewieController = ChewieController( 66 | videoPlayerController: videoPlayerController, 67 | autoPlay: false, 68 | allowFullScreen: false, 69 | fullScreenByDefault: false, 70 | autoInitialize: false, 71 | ); 72 | }); 73 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); 74 | SystemChrome.setPreferredOrientations([ 75 | DeviceOrientation.landscapeLeft, 76 | DeviceOrientation.landscapeRight, 77 | ]); 78 | 79 | super.initState(); 80 | } 81 | 82 | void disposeVideoController() async { 83 | await videoPlayerController.stopRendererScanning(); 84 | await videoPlayerController.dispose(); 85 | chewieController.dispose(); 86 | } 87 | 88 | @override 89 | void deactivate() { 90 | SystemChrome.setPreferredOrientations([ 91 | DeviceOrientation.portraitDown, 92 | DeviceOrientation.portraitUp, 93 | ]); 94 | disposeVideoController(); 95 | super.deactivate(); 96 | } 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | return Scaffold( 101 | body: Stack( 102 | children: [ 103 | Chewie( 104 | controller: chewieController, 105 | ) 106 | ], 107 | )); 108 | } 109 | } 110 | 111 | class MyHomePage extends StatefulWidget { 112 | const MyHomePage({Key? key, required this.title}) : super(key: key); 113 | 114 | // This widget is the home page of your application. It is stateful, meaning 115 | // that it has a State object (defined below) that contains fields that affect 116 | // how it looks. 117 | 118 | // This class is the configuration for the state. It holds the values (in this 119 | // case the title) provided by the parent (in this case the App widget) and 120 | // used by the build method of the State. Fields in a Widget subclass are 121 | // always marked "final". 122 | 123 | final String title; 124 | 125 | @override 126 | State createState() => _MyHomePageState(); 127 | } 128 | 129 | class _MyHomePageState extends State { 130 | final _controller = TextEditingController(); 131 | Timer? _debounce; 132 | bool isSearching = false; 133 | List> animeList = []; 134 | List> episodes = []; 135 | 136 | @override 137 | void initState() { 138 | SystemChrome.setPreferredOrientations([ 139 | DeviceOrientation.portraitDown, 140 | DeviceOrientation.portraitUp, 141 | ]); 142 | 143 | _controller.addListener(() { 144 | if (_debounce?.isActive ?? false) _debounce?.cancel(); 145 | _debounce = Timer(const Duration(milliseconds: 1000), () { 146 | setState(() { 147 | isSearching = true; 148 | }); 149 | searchAnime(_controller.value.text); 150 | }); 151 | }); 152 | 153 | super.initState(); 154 | } 155 | 156 | void searchAnime(String text) async { 157 | final searchValue = text.split(" ").join("-"); 158 | 159 | if (text != "") { 160 | final result = await scraper.searchAnime(searchValue); 161 | setState(() { 162 | animeList = result; 163 | isSearching = false; 164 | }); 165 | } else { 166 | setState(() { 167 | isSearching = false; 168 | }); 169 | } 170 | } 171 | 172 | void displayEpisodes(String anime) async { 173 | showDialog( 174 | context: context, 175 | builder: (_) { 176 | return const SizedBox( 177 | child: Center(child: CircularProgressIndicator()), 178 | width: 10, 179 | height: 10, 180 | ); 181 | }); 182 | var eps = await scraper.getEpisodes(anime); 183 | Navigator.pop(context); 184 | showDialog( 185 | context: context, 186 | builder: (BuildContext build) { 187 | return AlertDialog( 188 | title: const Text("Episodes"), 189 | content: ListView.separated( 190 | itemBuilder: (_, i) { 191 | final episode = eps[i].entries.first.key; 192 | final cover = eps[i].entries.first.value; 193 | return ListCard(cover, episode, () { 194 | streamEpisode(episode); 195 | }, 196 | const TextStyle(), 197 | const EdgeInsets.only( 198 | left: 10, right: 0, top: 0, bottom: 0)); 199 | }, 200 | itemCount: eps.length, 201 | padding: const EdgeInsets.all(8), 202 | scrollDirection: Axis.vertical, 203 | shrinkWrap: true, 204 | separatorBuilder: (_, i) { 205 | return const Divider(); 206 | }, 207 | )); 208 | }); 209 | } 210 | 211 | void streamEpisode(String episode) async { 212 | final downloadLink = await scraper.getDpageLink(episode); 213 | 214 | final data = await scraper.extractKeys("https:" + downloadLink); 215 | final id = data.keys.first; 216 | final keyData = data.values.first; 217 | final streamLink = await scraper.decryptLink(keyData.token,id,keyData.decryptKey,keyData.iv); 218 | 219 | Navigator.of(context).push(MaterialPageRoute( 220 | builder: (BuildContext context) => Episode(streamLink: streamLink), 221 | fullscreenDialog: true, 222 | )); 223 | } 224 | 225 | @override 226 | void dispose() { 227 | _debounce?.cancel(); 228 | super.dispose(); 229 | } 230 | 231 | @override 232 | Widget build(BuildContext context) { 233 | // This method is rerun every time setState is called, for instance as done 234 | // by the _incrementCounter method above. 235 | // 236 | // The Flutter framework has been optimized to make rerunning build methods 237 | // fast, so that you can just rebuild anything that needs updating rather 238 | // than having to individually change instances of widgets. 239 | return Scaffold( 240 | appBar: AppBar( 241 | // Here we take the value from the MyHomePage object that was created by 242 | // the App.build method, and use it to set our appbar title. 243 | title: Text(widget.title), 244 | ), 245 | body: Center( 246 | // Center is a layout widget. It takes a single child and positions it 247 | // in the middle of the parent. 248 | child: Column( 249 | // Column is also a layout widget. It takes a list of children and 250 | // arranges them vertically. By default, it sizes itself to fit its 251 | // children horizontally, and tries to be as tall as its parent. 252 | // 253 | // Invoke "debug painting" (press "p" in the console, choose the 254 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 255 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 256 | // to see the wireframe for each widget. 257 | // 258 | // Column has various properties to control how it sizes itself and 259 | // how it positions its children. Here we use mainAxisAlignment to 260 | // center the children vertically; the main axis here is the vertical 261 | // axis because Columns are vertical (the cross axis would be 262 | // horizontal). 263 | mainAxisAlignment: MainAxisAlignment.start, 264 | children: [ 265 | TextField( 266 | autocorrect: true, 267 | decoration: const InputDecoration( 268 | hintText: 'Search for anime', 269 | hintStyle: TextStyle(color: Colors.grey), 270 | filled: true, 271 | fillColor: Colors.white, 272 | contentPadding: EdgeInsets.fromLTRB(10, 0, 0, 0), 273 | enabledBorder: OutlineInputBorder( 274 | borderRadius: BorderRadius.all(Radius.circular(7.0)), 275 | borderSide: BorderSide(color: Colors.blue, width: 2), 276 | ), 277 | focusedBorder: OutlineInputBorder( 278 | borderRadius: BorderRadius.all(Radius.circular(2.0)), 279 | borderSide: BorderSide(color: Colors.blue), 280 | ), 281 | ), 282 | controller: _controller, 283 | ), 284 | const Divider(), 285 | isSearching 286 | ? const SizedBox( 287 | child: Center(child: CircularProgressIndicator()), 288 | width: 50, 289 | height: 50, 290 | ) 291 | : Expanded( 292 | child: ListView.separated( 293 | itemCount: animeList.length, 294 | itemBuilder: (_, i) { 295 | final anime = animeList[i].entries.first.key; 296 | final cover = animeList[i].entries.first.value; 297 | return ListCard(cover, anime, () { 298 | displayEpisodes(anime); 299 | }, 300 | const TextStyle(fontSize: 23), 301 | const EdgeInsets.only( 302 | left: 0, top: 0, right: 40, bottom: 40)); 303 | }, 304 | separatorBuilder: (context, _) { 305 | return const Divider(); 306 | }, 307 | shrinkWrap: true, 308 | )) 309 | ], 310 | ), 311 | ), 312 | drawer: 313 | SettingsDrawer(), // This trailing comma makes auto-formatting nicer for build methods. 314 | ); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /lib/scraper.dart: -------------------------------------------------------------------------------- 1 | library ani_app.scraper; 2 | 3 | import 'dart:convert'; 4 | import 'dart:io'; 5 | 6 | import 'constants.dart' as constants; 7 | import 'utils.dart' as utils; 8 | import 'types.dart'; 9 | 10 | import 'package:html/parser.dart' show parse; 11 | import 'package:html/dom.dart'; 12 | 13 | final httpClient = HttpClient(); 14 | 15 | Future>> searchAnime(String name) async { 16 | final req = await httpClient 17 | .getUrl(Uri.parse("${constants.baseUrl}/search.html?keyword=$name")); 18 | 19 | req.headers.set("User-Agent", constants.userAgent); 20 | 21 | final res = await req.close(); 22 | 23 | final html = (await res.transform(utf8.decoder).join()).parseString(); 24 | 25 | final animeList = html.querySelectorAll('a').where((x) { 26 | return x.attributes["href"]!.contains("/videos/"); 27 | }).map((x) { 28 | final anime = x.attributes["href"]!; 29 | final cover = x.querySelector('img')?.attributes["src"]!; 30 | return {anime: cover!}; 31 | }).toList(); 32 | 33 | return animeList; 34 | } 35 | 36 | Future>> getEpisodes(String animeId) async { 37 | final splitAnimeId = animeId.split('/'); 38 | 39 | final animeTempId = 40 | "/videos/${(splitAnimeId[2] = (splitAnimeId[2].split('-')..removeLast()).join('-'))}"; 41 | 42 | final req = 43 | await httpClient.getUrl(Uri.parse("${constants.baseUrl}/$animeId")); 44 | 45 | req.headers.set("User-Agent", constants.userAgent); 46 | 47 | final res = await req.close(); 48 | 49 | final html = (await res.transform(utf8.decoder).join()).parseString(); 50 | 51 | final episodeList = html.querySelectorAll('a').reversed.where((x) { 52 | return x.attributes["href"]!.contains(animeTempId); 53 | }).map((x) { 54 | final episode = x.attributes["href"]!; 55 | final cover = x.querySelector('img')?.attributes["src"]!; 56 | return {episode: cover!}; 57 | }).toList(); 58 | 59 | return episodeList; 60 | } 61 | 62 | Future getDpageLink(String episodeLink) async { 63 | final req = 64 | await httpClient.getUrl(Uri.parse("${constants.baseUrl}/$episodeLink")); 65 | 66 | req.headers.set("User-Agent", constants.userAgent); 67 | 68 | final res = await req.close(); 69 | 70 | final html = (await res.transform(utf8.decoder).join()).parseString(); 71 | 72 | final link = html.querySelector('iframe')?.attributes["src"]!; 73 | 74 | if (link != null) { 75 | return link; 76 | } 77 | 78 | return ""; 79 | } 80 | 81 | Future> extractKeys(String downloadLink) async { 82 | final req = await httpClient.getUrl(Uri.parse(downloadLink)); 83 | req.headers.set("User-Agent", constants.userAgent); 84 | 85 | final res = await req.close(); 86 | 87 | final html = (await res.transform(utf8.decoder).join()).parseString(); 88 | 89 | final keyData = utils.KeyData.scrapeKeys(html); 90 | 91 | final id = downloadLink.split("?")[1].split("=")[1]; 92 | 93 | // final decoded = base64.decode(keyData.secretValue); 94 | 95 | // final secretData = utils.decode(decoded, keyData.key, keyData.iv); 96 | 97 | // final alias = secretData.substring(0,secretData.indexOf("&")); https://goload.pro/streaming.php?id=MjYzMw==&title=Shingeki+no+Kyojin&typesub=SUB&sub=eyJlbiI6bnVsbCwiZXMiOm51bGx9&cover=aW1hZ2VzL2FuaW1lL1NoaW5nZWtpLW5vLUt5b2ppbi5qcGc= 98 | 99 | final encryptedId = 100 | utils.encodeToBase64(id, keyData.secretValue, keyData.iv); 101 | 102 | return { 103 | encryptedId: keyData, 104 | }; 105 | } 106 | 107 | Future decryptLink(utils.TokenData token, 108 | String id, String key, String iv) async { 109 | final req = await httpClient.getUrl(Uri.parse( 110 | "${constants.decryptionUrl}?id=$id&alias=${token.alias}&$token")); 111 | 112 | req.headers.set("User-Agent", constants.userAgent); 113 | req.headers.add("X-Requested-With", "XMLHttpRequest"); 114 | 115 | final res = await req.close(); 116 | 117 | final data = (await res.transform(utf8.decoder).join()); 118 | 119 | final base64Data = data 120 | .split(":")[1] 121 | .replaceAll("}", "") 122 | .replaceAll("\"", "") 123 | .replaceAll("\\", ""); 124 | 125 | final decrypted = base64.decode(base64Data); 126 | 127 | final decryptedData = streamFromJson(utils.decode(decrypted, key, iv)); 128 | 129 | return decryptedData.source.first.file; 130 | } 131 | -------------------------------------------------------------------------------- /lib/types.dart: -------------------------------------------------------------------------------- 1 | // To parse this JSON data, do 2 | // 3 | // final stream = streamFromJson(jsonString); 4 | 5 | import 'dart:convert'; 6 | 7 | Stream streamFromJson(String str) => Stream.fromJson(json.decode(str)); 8 | 9 | String streamToJson(Stream data) => json.encode(data.toJson()); 10 | 11 | class Stream { 12 | Stream({ 13 | required this.source, 14 | required this.sourceBk, 15 | // required this.track, 16 | // required this.advertising, 17 | required this.linkiframe, 18 | }); 19 | 20 | List source; 21 | List sourceBk; 22 | // StreamTrack track; 23 | // List advertising; 24 | String linkiframe; 25 | 26 | factory Stream.fromJson(Map json) => Stream( 27 | source: List.from(json["source"].map((x) => Source.fromJson(x))), 28 | sourceBk: List.from(json["source_bk"].map((x) => Source.fromJson(x))), 29 | // track: StreamTrack.fromJson(json["track"]), 30 | // advertising: List.from(json["advertising"].map((x) => x)), 31 | linkiframe: json["linkiframe"], 32 | ); 33 | 34 | Map toJson() => { 35 | "source": List.from(source.map((x) => x.toJson())), 36 | "source_bk": List.from(sourceBk.map((x) => x.toJson())), 37 | // "track": track.toJson(), 38 | // "advertising": List.from(advertising.map((x) => x)), 39 | "linkiframe": linkiframe, 40 | }; 41 | } 42 | 43 | class Source { 44 | Source({ 45 | required this.file, 46 | required this.label, 47 | required this.type, 48 | }); 49 | 50 | String file; 51 | String label; 52 | String type; 53 | 54 | factory Source.fromJson(Map json) => Source( 55 | file: json["file"], 56 | label: json["label"], 57 | type: json["type"], 58 | ); 59 | 60 | Map toJson() => { 61 | "file": file, 62 | "label": label, 63 | "type": type, 64 | }; 65 | } 66 | 67 | class StreamTrack { 68 | StreamTrack({ 69 | required this.tracks, 70 | }); 71 | 72 | List tracks; 73 | 74 | factory StreamTrack.fromJson(Map json) => StreamTrack( 75 | tracks: List.from(json["tracks"].map((x) => TrackElement.fromJson(x))), 76 | ); 77 | 78 | Map toJson() => { 79 | "tracks": List.from(tracks.map((x) => x.toJson())), 80 | }; 81 | } 82 | 83 | class TrackElement { 84 | TrackElement({ 85 | required this.file, 86 | required this.kind, 87 | }); 88 | 89 | String file; 90 | String kind; 91 | 92 | factory TrackElement.fromJson(Map json) => TrackElement( 93 | file: json["file"], 94 | kind: json["kind"], 95 | ); 96 | 97 | Map toJson() => { 98 | "file": file, 99 | "kind": kind, 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /lib/utils.dart: -------------------------------------------------------------------------------- 1 | library ani_app.utils; 2 | 3 | import 'dart:convert'; 4 | import 'dart:typed_data'; 5 | import "package:pointycastle/export.dart"; 6 | 7 | import 'package:html/dom.dart'; 8 | import 'package:html/parser.dart'; 9 | 10 | import 'constants.dart' as constants; 11 | 12 | import 'dart:math'; 13 | 14 | extension Parser on String { 15 | Document parseString() { 16 | return parse(this); 17 | } 18 | 19 | WordArray parseHexString() { 20 | final latin1StrLength = length; 21 | 22 | // Convert 23 | List words = 24 | List.generate(latin1StrLength >>> 2, (index) => 0, growable: false); 25 | for (var i = 0; i < latin1StrLength; i++) { 26 | words[i >>> 2] |= (codeUnitAt(i) & 0xff) << (24 - (i % 4) * 8); 27 | } 28 | 29 | return WordArray(words, latin1StrLength); 30 | } 31 | 32 | Uint8List decode() { 33 | return base64.decode(this); 34 | } 35 | } 36 | 37 | class WordArray { 38 | WordArray(this.words, this.sigBytes); 39 | 40 | final List words; 41 | final int sigBytes; 42 | 43 | String stringify() { 44 | // Convert 45 | var hexChars = []; 46 | for (var i = 0; i < sigBytes; i++) { 47 | var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; 48 | hexChars.add((bite >>> 4).toRadixString(16)); 49 | hexChars.add((bite & 0x0f).toRadixString(16)); 50 | } 51 | 52 | return hexChars.join(''); 53 | } 54 | } 55 | 56 | String decodeKey(String id, String iv) { 57 | final decoded = utf8.decode(base64.decode(id)); 58 | 59 | final value = decoded + "" + iv; 60 | 61 | final bytes = 62 | List.generate(value.length, (index) => index, growable: false); 63 | 64 | return bytes 65 | .map((x) => value.codeUnitAt(x).toRadixString(16)) 66 | .join("") 67 | .substring(0, 32) 68 | .parseHexString() 69 | .stringify(); 70 | } 71 | 72 | class TokenData { 73 | String alias = ""; 74 | String token = ""; 75 | String expires = ""; 76 | String op = ""; 77 | 78 | TokenData(this.alias, this.token, this.expires,this.op); 79 | 80 | @override 81 | String toString() { 82 | // TODO: implement toString 83 | return "$token&$expires&$op"; 84 | } 85 | } 86 | 87 | class KeyData { 88 | String iv = ""; 89 | String key = ""; 90 | String secretValue = ""; 91 | TokenData token = TokenData("", "", "",""); 92 | 93 | String decryptKey = ""; 94 | 95 | KeyData(this.iv, this.key, this.secretValue, this.decryptKey, this.token); 96 | 97 | factory KeyData.scrapeKeys(Document html) { 98 | final token = html 99 | .querySelector('script[data-name="episode"]') 100 | ?.attributes["data-value"]! 101 | .decode(); 102 | 103 | final secretValue = html 104 | .querySelector("body[class*='container-']") 105 | ?.attributes["class"]! 106 | .split("-") 107 | .removeLast() 108 | .parseHexString() 109 | .stringify(); 110 | 111 | final alias = html.querySelector("#id")?.attributes["value"]!; 112 | 113 | final key = html 114 | .querySelector(constants.keyPath) 115 | ?.attributes["class"]! 116 | .split('-') 117 | .removeLast() 118 | .parseHexString() 119 | .stringify(); 120 | 121 | final secondKey = html 122 | .querySelector(constants.secondKeyPath) 123 | ?.attributes["class"]! 124 | .split('-') 125 | .removeLast() 126 | .parseHexString() 127 | .stringify(); 128 | 129 | final keyIV = html 130 | .querySelector("div[class*='container-']") 131 | ?.attributes["class"]! 132 | .split('-') 133 | .removeLast() 134 | .parseHexString() 135 | .stringify(); 136 | 137 | if (secretValue != null && 138 | key != null && 139 | keyIV != null && 140 | secondKey != null && 141 | alias != null) { 142 | final decodedToken = decode(token!, secretValue, keyIV); 143 | 144 | final tokenParts = decodedToken 145 | .split("&") 146 | .where((x) => x.contains("token") || x.contains("expires") || x.contains("op")) 147 | .toList(); 148 | // var key = decodeKey(alias, keyIV); 149 | return KeyData(keyIV, key, secretValue, secondKey, 150 | TokenData(alias, tokenParts.first,tokenParts[1], tokenParts.last)); 151 | } 152 | 153 | return KeyData("", "", "", "", TokenData("", "", "","")); 154 | } 155 | } 156 | 157 | Uint8List pad(Uint8List src, int blockSize) { 158 | var pad = PKCS7Padding(); 159 | pad.init(null); 160 | 161 | int padLength = blockSize - (src.length % blockSize); 162 | var out = Uint8List(src.length + padLength)..setAll(0, src); 163 | pad.addPadding(out, src.length); 164 | 165 | return out; 166 | } 167 | 168 | Uint8List createUint8ListFromString(String s) { 169 | var ret = Uint8List(s.length); 170 | for (var i = 0; i < s.length; i++) { 171 | ret[i] = s.codeUnitAt(i); 172 | } 173 | return ret; 174 | } 175 | 176 | Uint8List createUint8ListFromHexString(String hex) { 177 | var result = Uint8List(hex.length ~/ 2); 178 | for (var i = 0; i < hex.length; i += 2) { 179 | var num = hex.substring(i, i + 2); 180 | var byte = int.parse(num, radix: 16); 181 | result[i ~/ 2] = byte; 182 | } 183 | return result; 184 | } 185 | 186 | String encodeToBase64(String plainText, String plainKey, String plainIv) { 187 | final key = createUint8ListFromHexString(plainKey); 188 | final iv = createUint8ListFromHexString(plainIv); 189 | 190 | final engine = AESEngine(); 191 | 192 | final cbc = CBCBlockCipher(engine) 193 | ..init(true, ParametersWithIV(KeyParameter(key), iv)); 194 | 195 | final paddedText = 196 | pad(createUint8ListFromString(plainText), engine.blockSize); 197 | 198 | final cipherText = Uint8List(paddedText.length); 199 | 200 | var offset = 0; 201 | while (offset < paddedText.length) { 202 | offset += cbc.processBlock(paddedText, offset, cipherText, offset); 203 | } 204 | 205 | return base64.encode(cipherText); 206 | } 207 | 208 | String decode(Uint8List cipherText, String plainKey, String plainIv) { 209 | final key = createUint8ListFromHexString(plainKey); 210 | final iv = createUint8ListFromHexString(plainIv); 211 | 212 | final engine = AESEngine(); 213 | 214 | final cbc = CBCBlockCipher(engine) 215 | ..init(false, ParametersWithIV(KeyParameter(key), iv)); 216 | 217 | final paddedText = Uint8List(cipherText.length); 218 | 219 | var offset = 0; 220 | while (offset < cipherText.length) { 221 | offset += cbc.processBlock(cipherText, offset, paddedText, offset); 222 | } 223 | 224 | final minByte = paddedText.reduce((value, element) { 225 | return min(value, element); 226 | }); 227 | 228 | final cleanText = paddedText.where((x) { 229 | return !(x == minByte); 230 | }).toList(); 231 | 232 | return utf8.decode(cleanText).replaceAll('\f', ""); 233 | } 234 | -------------------------------------------------------------------------------- /lib/widgets.dart: -------------------------------------------------------------------------------- 1 | library ani_app.widgets; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'package:ani_app/constants.dart' as constants; 7 | 8 | Widget SettingsDrawer() { 9 | return Drawer( 10 | child: ListView.separated( 11 | padding: EdgeInsets.zero, 12 | separatorBuilder: (_, i) { 13 | return const Divider(); 14 | }, 15 | itemCount: constants.settings.length, 16 | itemBuilder: (BuildContext context, int index) { 17 | if (index == 0) { 18 | return const DrawerHeader( 19 | child: Text('Settings'), 20 | decoration: BoxDecoration( 21 | color: Colors.blue, 22 | ), 23 | ); 24 | } 25 | return ListTile(title: Text(constants.settings[index])); 26 | }, 27 | )); 28 | } 29 | 30 | Widget ListCard( 31 | String cover, String title, void Function() onTap, TextStyle textStyle,EdgeInsetsGeometry padding) { 32 | return GestureDetector( 33 | onTap: onTap, 34 | child: Flex( 35 | direction: Axis.horizontal, 36 | children: [ 37 | ConstrainedBox( 38 | constraints: const BoxConstraints( 39 | minWidth: 100, 40 | minHeight: 100, 41 | maxWidth: 100, 42 | maxHeight: 200, 43 | ), 44 | child: Image.network( 45 | cover, 46 | fit: BoxFit.cover, 47 | )), 48 | Expanded( 49 | child: Container( 50 | child: Text( 51 | title.split('/')[2], 52 | textAlign: TextAlign.center, 53 | style: textStyle 54 | ), 55 | padding: padding 56 | ), 57 | ), 58 | const Icon(Icons.star_border_outlined,color: Colors.blueGrey,) 59 | // const Divider() 60 | ], 61 | )); 62 | } 63 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | chewie: 33 | dependency: "direct main" 34 | description: 35 | path: "." 36 | ref: vlc_player 37 | resolved-ref: "3603406ef4e438c8590da04fa69c6caf61452b78" 38 | url: "https://github.com/mdrokz/chewie.git" 39 | source: git 40 | version: "1.3.1" 41 | clock: 42 | dependency: transitive 43 | description: 44 | name: clock 45 | url: "https://pub.dartlang.org" 46 | source: hosted 47 | version: "1.1.0" 48 | collection: 49 | dependency: transitive 50 | description: 51 | name: collection 52 | url: "https://pub.dartlang.org" 53 | source: hosted 54 | version: "1.15.0" 55 | convert: 56 | dependency: transitive 57 | description: 58 | name: convert 59 | url: "https://pub.dartlang.org" 60 | source: hosted 61 | version: "3.0.1" 62 | csslib: 63 | dependency: transitive 64 | description: 65 | name: csslib 66 | url: "https://pub.dartlang.org" 67 | source: hosted 68 | version: "0.17.1" 69 | cupertino_icons: 70 | dependency: "direct main" 71 | description: 72 | name: cupertino_icons 73 | url: "https://pub.dartlang.org" 74 | source: hosted 75 | version: "1.0.4" 76 | fake_async: 77 | dependency: transitive 78 | description: 79 | name: fake_async 80 | url: "https://pub.dartlang.org" 81 | source: hosted 82 | version: "1.2.0" 83 | ffi: 84 | dependency: transitive 85 | description: 86 | name: ffi 87 | url: "https://pub.dartlang.org" 88 | source: hosted 89 | version: "1.1.2" 90 | flutter: 91 | dependency: "direct main" 92 | description: flutter 93 | source: sdk 94 | version: "0.0.0" 95 | flutter_lints: 96 | dependency: "direct dev" 97 | description: 98 | name: flutter_lints 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "1.0.4" 102 | flutter_test: 103 | dependency: "direct dev" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_vlc_player: 108 | dependency: "direct main" 109 | description: 110 | name: flutter_vlc_player 111 | url: "https://pub.dartlang.org" 112 | source: hosted 113 | version: "7.1.0" 114 | flutter_vlc_player_platform_interface: 115 | dependency: transitive 116 | description: 117 | name: flutter_vlc_player_platform_interface 118 | url: "https://pub.dartlang.org" 119 | source: hosted 120 | version: "2.0.0" 121 | flutter_web_plugins: 122 | dependency: transitive 123 | description: flutter 124 | source: sdk 125 | version: "0.0.0" 126 | html: 127 | dependency: "direct main" 128 | description: 129 | name: html 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "0.15.0" 133 | js: 134 | dependency: transitive 135 | description: 136 | name: js 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "0.6.3" 140 | lints: 141 | dependency: transitive 142 | description: 143 | name: lints 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "1.0.1" 147 | matcher: 148 | dependency: transitive 149 | description: 150 | name: matcher 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.12.11" 154 | material_color_utilities: 155 | dependency: transitive 156 | description: 157 | name: material_color_utilities 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.1.3" 161 | meta: 162 | dependency: transitive 163 | description: 164 | name: meta 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "1.7.0" 168 | nested: 169 | dependency: transitive 170 | description: 171 | name: nested 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "1.0.0" 175 | path: 176 | dependency: transitive 177 | description: 178 | name: path 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "1.8.0" 182 | plugin_platform_interface: 183 | dependency: transitive 184 | description: 185 | name: plugin_platform_interface 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "2.1.2" 189 | pointycastle: 190 | dependency: "direct main" 191 | description: 192 | name: pointycastle 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "3.5.2" 196 | provider: 197 | dependency: transitive 198 | description: 199 | name: provider 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "6.0.2" 203 | sky_engine: 204 | dependency: transitive 205 | description: flutter 206 | source: sdk 207 | version: "0.0.99" 208 | source_span: 209 | dependency: transitive 210 | description: 211 | name: source_span 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.8.1" 215 | stack_trace: 216 | dependency: transitive 217 | description: 218 | name: stack_trace 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "1.10.0" 222 | stream_channel: 223 | dependency: transitive 224 | description: 225 | name: stream_channel 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.1.0" 229 | string_scanner: 230 | dependency: transitive 231 | description: 232 | name: string_scanner 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "1.1.0" 236 | term_glyph: 237 | dependency: transitive 238 | description: 239 | name: term_glyph 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "1.2.0" 243 | test_api: 244 | dependency: transitive 245 | description: 246 | name: test_api 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "0.4.8" 250 | typed_data: 251 | dependency: transitive 252 | description: 253 | name: typed_data 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "1.3.0" 257 | vector_math: 258 | dependency: transitive 259 | description: 260 | name: vector_math 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "2.1.1" 264 | very_good_analysis: 265 | dependency: transitive 266 | description: 267 | name: very_good_analysis 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "2.4.0" 271 | wakelock: 272 | dependency: transitive 273 | description: 274 | name: wakelock 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "0.6.1+2" 278 | wakelock_macos: 279 | dependency: transitive 280 | description: 281 | name: wakelock_macos 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "0.4.0" 285 | wakelock_platform_interface: 286 | dependency: transitive 287 | description: 288 | name: wakelock_platform_interface 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "0.3.0" 292 | wakelock_web: 293 | dependency: transitive 294 | description: 295 | name: wakelock_web 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "0.4.0" 299 | wakelock_windows: 300 | dependency: transitive 301 | description: 302 | name: wakelock_windows 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "0.2.0" 306 | win32: 307 | dependency: transitive 308 | description: 309 | name: win32 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "2.5.1" 313 | sdks: 314 | dart: ">=2.16.1 <3.0.0" 315 | flutter: ">=2.0.0" 316 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ani_app 2 | description: Based on ani-cli 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.16.1 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter: 31 | sdk: flutter 32 | 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^1.0.2 37 | html: ^0.15.0 38 | flutter_vlc_player: ^7.1.0 39 | chewie: 40 | git: 41 | url: https://github.com/mdrokz/chewie.git 42 | ref: vlc_player 43 | pointycastle: ^3.5.2 44 | 45 | dev_dependencies: 46 | flutter_test: 47 | sdk: flutter 48 | 49 | # The "flutter_lints" package below contains a set of recommended lints to 50 | # encourage good coding practices. The lint set provided by the package is 51 | # activated in the `analysis_options.yaml` file located at the root of your 52 | # package. See that file for information about deactivating specific lint 53 | # rules and activating additional ones. 54 | flutter_lints: ^1.0.0 55 | 56 | # For information on the generic Dart part of this file, see the 57 | # following page: https://dart.dev/tools/pub/pubspec 58 | 59 | # The following section is specific to Flutter. 60 | flutter: 61 | 62 | # The following line ensures that the Material Icons font is 63 | # included with your application, so that you can use the icons in 64 | # the material Icons class. 65 | uses-material-design: true 66 | 67 | # To add assets to your application, add an assets section, like this: 68 | # assets: 69 | # - images/a_dot_burr.jpeg 70 | # - images/a_dot_ham.jpeg 71 | 72 | # An image asset can refer to one or more resolution-specific "variants", see 73 | # https://flutter.dev/assets-and-images/#resolution-aware. 74 | 75 | # For details regarding adding assets from package dependencies, see 76 | # https://flutter.dev/assets-and-images/#from-packages 77 | 78 | # To add custom fonts to your application, add a fonts section here, 79 | # in this "flutter" section. Each entry in this list should have a 80 | # "family" key with the font family name, and a "fonts" key with a 81 | # list giving the asset and other descriptors for the font. For 82 | # example: 83 | # fonts: 84 | # - family: Schyler 85 | # fonts: 86 | # - asset: fonts/Schyler-Regular.ttf 87 | # - asset: fonts/Schyler-Italic.ttf 88 | # style: italic 89 | # - family: Trajan Pro 90 | # fonts: 91 | # - asset: fonts/TrajanPro.ttf 92 | # - asset: fonts/TrajanPro_Bold.ttf 93 | # weight: 700 94 | # 95 | # For details regarding fonts from package dependencies, 96 | # see https://flutter.dev/custom-fonts/#from-packages 97 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:ani_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------