├── android ├── gradle.properties ├── .settings │ └── org.eclipse.buildship.core.prefs ├── app │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── 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 │ │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── flutter_tubi │ │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── .classpath │ ├── .project │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .project ├── settings.gradle └── build.gradle ├── assets ├── fonts │ ├── Vaud.otf │ ├── Vaud-Bold.ttf │ ├── Vaud-Thin.otf │ ├── Vaud-Light.otf │ ├── Vaud-Medium.otf │ ├── Vaud-Ultra.otf │ ├── Vaud-SemiBold.otf │ └── Vaud-UltraLight.otf └── icons │ ├── home_normal.png │ ├── placeholder.png │ ├── account_normal.png │ ├── browse_normal.png │ ├── fwd_15_normal.png │ ├── home_selected.png │ ├── iconOverflow.png │ ├── icon_nav_logo.png │ ├── pause_normal.png │ ├── rew_15_normal.png │ ├── search_normal.png │ ├── share_normal.png │ ├── 2.0x │ ├── home_normal.png │ ├── placeholder.png │ ├── account_normal.png │ ├── browse_normal.png │ ├── fwd_15_normal.png │ ├── home_selected.png │ ├── iconOverflow.png │ ├── icon_nav_logo.png │ ├── pause_normal.png │ ├── rew_15_normal.png │ ├── search_normal.png │ ├── share_normal.png │ ├── account_selected.png │ ├── browse_selected.png │ ├── search_selected.png │ ├── add_to_queue_normal.png │ ├── play_large_normal.png │ └── play_small_normal.png │ ├── 3.0x │ ├── home_normal.png │ ├── placeholder.png │ ├── account_normal.png │ ├── browse_normal.png │ ├── fwd_15_normal.png │ ├── home_selected.png │ ├── iconOverflow.png │ ├── icon_nav_logo.png │ ├── pause_normal.png │ ├── rew_15_normal.png │ ├── search_normal.png │ ├── share_normal.png │ ├── account_selected.png │ ├── browse_selected.png │ ├── search_selected.png │ ├── add_to_queue_normal.png │ ├── play_large_normal.png │ └── play_small_normal.png │ ├── account_selected.png │ ├── browse_selected.png │ ├── search_selected.png │ ├── add_to_queue_normal.png │ ├── play_large_normal.png │ ├── play_small_normal.png │ └── movie_section_header_dot.png ├── screenshot ├── ios │ ├── home.jpg │ ├── browse.jpg │ ├── search.jpg │ ├── category.jpg │ ├── video_player.jpg │ ├── movie_details.jpg │ └── search_result.jpg └── android │ ├── home.jpg │ ├── browse.jpg │ ├── category.jpg │ ├── search.jpg │ ├── movie_details.jpg │ ├── search_result.jpg │ └── video_player.jpg ├── lib ├── src │ ├── blocs │ │ ├── home │ │ │ ├── bloc.dart │ │ │ ├── home_event.dart │ │ │ ├── home_state.dart │ │ │ └── home_bloc.dart │ │ ├── related_movies │ │ │ ├── bloc.dart │ │ │ ├── related_event.dart │ │ │ ├── related_state.dart │ │ │ └── related_bloc.dart │ │ ├── bloc_event.dart │ │ ├── bloc_state.dart │ │ ├── search_bloc.dart │ │ └── movie_details_bloc.dart │ ├── repositories │ │ ├── repositories.dart │ │ ├── home_page_repository.dart │ │ ├── related_movies_repository.dart │ │ └── http_client.dart │ ├── models │ │ ├── models.dart │ │ ├── movie_list.dart │ │ ├── video_resource.dart │ │ ├── movie_list.g.dart │ │ ├── video_resource.g.dart │ │ ├── home.dart │ │ ├── movie.dart │ │ └── movie.g.dart │ ├── ui │ │ ├── video_player │ │ │ ├── share_data_widget.dart │ │ │ ├── video_paly_controls.dart │ │ │ ├── video_duration_toolbar.dart │ │ │ └── video_player.dart │ │ ├── home │ │ │ ├── home_page.dart │ │ │ ├── home_movie_scroll_row.dart │ │ │ ├── home_list_view.dart │ │ │ └── home_featured_row.dart │ │ ├── category │ │ │ └── movie_category.dart │ │ ├── browse │ │ │ └── browse_page.dart │ │ ├── search │ │ │ └── search_page.dart │ │ └── movie_page │ │ │ └── movie_page.dart │ ├── bottom_navigation.dart │ ├── tab_navigator.dart │ └── app.dart └── main.dart ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ ├── Flutter.podspec │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76~ipad.png │ │ │ ├── Icon-App-76x76@2x~ipad.png │ │ │ ├── Icon-App-83.5x83.5@2x~ipad.png │ │ │ └── Contents.json │ ├── main.m │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── project.pbxproj ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Podfile.lock └── Podfile ├── .metadata ├── test └── widget_test.dart ├── .flutter-plugins-dependencies ├── README.md ├── .gitignore ├── pubspec.yaml └── pubspec.lock /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /assets/fonts/Vaud.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud.otf -------------------------------------------------------------------------------- /screenshot/ios/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/home.jpg -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /assets/fonts/Vaud-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Vaud-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-Thin.otf -------------------------------------------------------------------------------- /screenshot/ios/browse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/browse.jpg -------------------------------------------------------------------------------- /screenshot/ios/search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/search.jpg -------------------------------------------------------------------------------- /android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /assets/fonts/Vaud-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-Light.otf -------------------------------------------------------------------------------- /assets/fonts/Vaud-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-Medium.otf -------------------------------------------------------------------------------- /assets/fonts/Vaud-Ultra.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-Ultra.otf -------------------------------------------------------------------------------- /assets/icons/home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/home_normal.png -------------------------------------------------------------------------------- /assets/icons/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/placeholder.png -------------------------------------------------------------------------------- /lib/src/blocs/home/bloc.dart: -------------------------------------------------------------------------------- 1 | export './home_event.dart'; 2 | export './home_state.dart'; 3 | export './home_bloc.dart'; -------------------------------------------------------------------------------- /screenshot/android/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/home.jpg -------------------------------------------------------------------------------- /screenshot/ios/category.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/category.jpg -------------------------------------------------------------------------------- /assets/fonts/Vaud-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-SemiBold.otf -------------------------------------------------------------------------------- /assets/icons/account_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/account_normal.png -------------------------------------------------------------------------------- /assets/icons/browse_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/browse_normal.png -------------------------------------------------------------------------------- /assets/icons/fwd_15_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/fwd_15_normal.png -------------------------------------------------------------------------------- /assets/icons/home_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/home_selected.png -------------------------------------------------------------------------------- /assets/icons/iconOverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/iconOverflow.png -------------------------------------------------------------------------------- /assets/icons/icon_nav_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/icon_nav_logo.png -------------------------------------------------------------------------------- /assets/icons/pause_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/pause_normal.png -------------------------------------------------------------------------------- /assets/icons/rew_15_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/rew_15_normal.png -------------------------------------------------------------------------------- /assets/icons/search_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/search_normal.png -------------------------------------------------------------------------------- /assets/icons/share_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/share_normal.png -------------------------------------------------------------------------------- /screenshot/android/browse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/browse.jpg -------------------------------------------------------------------------------- /screenshot/android/category.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/category.jpg -------------------------------------------------------------------------------- /screenshot/android/search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/search.jpg -------------------------------------------------------------------------------- /screenshot/ios/video_player.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/video_player.jpg -------------------------------------------------------------------------------- /assets/fonts/Vaud-UltraLight.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/fonts/Vaud-UltraLight.otf -------------------------------------------------------------------------------- /assets/icons/2.0x/home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/home_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/placeholder.png -------------------------------------------------------------------------------- /assets/icons/3.0x/home_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/home_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/placeholder.png -------------------------------------------------------------------------------- /assets/icons/account_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/account_selected.png -------------------------------------------------------------------------------- /assets/icons/browse_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/browse_selected.png -------------------------------------------------------------------------------- /assets/icons/search_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/search_selected.png -------------------------------------------------------------------------------- /lib/src/repositories/repositories.dart: -------------------------------------------------------------------------------- 1 | export './home_page_repository.dart'; 2 | export './related_movies_repository.dart'; 3 | -------------------------------------------------------------------------------- /screenshot/ios/movie_details.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/movie_details.jpg -------------------------------------------------------------------------------- /screenshot/ios/search_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/ios/search_result.jpg -------------------------------------------------------------------------------- /assets/icons/2.0x/account_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/account_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/browse_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/browse_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/fwd_15_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/fwd_15_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/home_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/home_selected.png -------------------------------------------------------------------------------- /assets/icons/2.0x/iconOverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/iconOverflow.png -------------------------------------------------------------------------------- /assets/icons/2.0x/icon_nav_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/icon_nav_logo.png -------------------------------------------------------------------------------- /assets/icons/2.0x/pause_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/pause_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/rew_15_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/rew_15_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/search_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/search_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/share_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/share_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/account_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/account_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/browse_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/browse_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/fwd_15_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/fwd_15_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/home_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/home_selected.png -------------------------------------------------------------------------------- /assets/icons/3.0x/iconOverflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/iconOverflow.png -------------------------------------------------------------------------------- /assets/icons/3.0x/icon_nav_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/icon_nav_logo.png -------------------------------------------------------------------------------- /assets/icons/3.0x/pause_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/pause_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/rew_15_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/rew_15_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/search_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/search_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/share_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/share_normal.png -------------------------------------------------------------------------------- /assets/icons/add_to_queue_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/add_to_queue_normal.png -------------------------------------------------------------------------------- /assets/icons/play_large_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/play_large_normal.png -------------------------------------------------------------------------------- /assets/icons/play_small_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/play_small_normal.png -------------------------------------------------------------------------------- /screenshot/android/movie_details.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/movie_details.jpg -------------------------------------------------------------------------------- /screenshot/android/search_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/search_result.jpg -------------------------------------------------------------------------------- /screenshot/android/video_player.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/screenshot/android/video_player.jpg -------------------------------------------------------------------------------- /assets/icons/2.0x/account_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/account_selected.png -------------------------------------------------------------------------------- /assets/icons/2.0x/browse_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/browse_selected.png -------------------------------------------------------------------------------- /assets/icons/2.0x/search_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/search_selected.png -------------------------------------------------------------------------------- /assets/icons/3.0x/account_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/account_selected.png -------------------------------------------------------------------------------- /assets/icons/3.0x/browse_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/browse_selected.png -------------------------------------------------------------------------------- /assets/icons/3.0x/search_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/search_selected.png -------------------------------------------------------------------------------- /lib/src/blocs/related_movies/bloc.dart: -------------------------------------------------------------------------------- 1 | export './related_bloc.dart'; 2 | export './related_event.dart'; 3 | export './related_state.dart'; -------------------------------------------------------------------------------- /assets/icons/2.0x/add_to_queue_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/add_to_queue_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/play_large_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/play_large_normal.png -------------------------------------------------------------------------------- /assets/icons/2.0x/play_small_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/2.0x/play_small_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/add_to_queue_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/add_to_queue_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/play_large_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/play_large_normal.png -------------------------------------------------------------------------------- /assets/icons/3.0x/play_small_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/3.0x/play_small_normal.png -------------------------------------------------------------------------------- /assets/icons/movie_section_header_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/assets/icons/movie_section_header_dot.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /lib/src/models/models.dart: -------------------------------------------------------------------------------- 1 | export './home.dart'; 2 | export './movie.dart'; 3 | export './movie_list.dart'; 4 | export './video_resource.dart'; -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76~ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x~ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xieweizhi/tubi_tv_flutter/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x~ipad.png -------------------------------------------------------------------------------- /lib/src/blocs/bloc_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class BlocEvent extends Equatable {} 4 | 5 | class BlocEventFetch extends BlocEvent { 6 | @override 7 | String toString() => 'BlocEventFetch'; 8 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/blocs/home/home_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class HomeEvent extends Equatable {} 4 | 5 | class FetchHomePage extends HomeEvent { 6 | @override 7 | String toString() => 'FetchHomePage'; 8 | } -------------------------------------------------------------------------------- /lib/src/blocs/related_movies/related_event.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter_tubi/src/blocs/home/home_event.dart'; 3 | 4 | class FetchRelatedMoviesEvent extends HomeEvent { 5 | @override 6 | String toString() => 'FetchRelatedMoviesEvent'; 7 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 6 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/repositories/home_page_repository.dart: -------------------------------------------------------------------------------- 1 | import 'http_client.dart'; 2 | import '../models/models.dart'; 3 | 4 | class HomePageRepository { 5 | final apiClient = ApiClient(); 6 | 7 | Future getHomePageData() async { 8 | return apiClient.fetchHomePage(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /lib/src/repositories/related_movies_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | import 'http_client.dart'; 4 | import '../models/models.dart'; 5 | 6 | class RelatedRepository { 7 | final apiClient = ApiClient(); 8 | 9 | Future> getRelatedMovies({@required String id}) async { 10 | return apiClient.fetchRelatedMovies(id: id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'src/app.dart'; 3 | 4 | import 'package:bloc/bloc.dart'; 5 | 6 | void main() { 7 | BlocSupervisor().delegate = SimpleBlocDelegate(); 8 | 9 | return runApp(App()); 10 | } 11 | 12 | class SimpleBlocDelegate extends BlocDelegate { 13 | @override 14 | void onTransition(Transition transition) { 15 | print('Transition: $transition'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/flutter_tubi/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.flutter_tubi; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /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_test/flutter_test.dart'; 9 | 10 | void main() { 11 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 12 | // Build our app and trigger a frame. 13 | }); 14 | } 15 | 16 | class App { 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/ui/video_player/share_data_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:video_player/video_player.dart'; 3 | 4 | class ShareDataWidget extends InheritedWidget { 5 | final VideoPlayerValue data; 6 | 7 | ShareDataWidget({Key key, this.child, @required this.data}) 8 | : super(key: key, child: child); 9 | 10 | final Widget child; 11 | 12 | static ShareDataWidget of(BuildContext context) { 13 | return context.dependOnInheritedWidgetOfExactType(); 14 | } 15 | 16 | @override 17 | bool updateShouldNotify(ShareDataWidget oldWidget) { 18 | print("should notify"); 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/src/blocs/bloc_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | abstract class BlocState extends Equatable { 5 | T data; 6 | } 7 | 8 | class BlocStateUninitialized extends BlocState { 9 | @override 10 | String toString() => 'BlocStateUninitialized'; 11 | } 12 | 13 | class BlocStateLoading extends BlocState { 14 | @override 15 | String toString() => 'BlocStateLoading'; 16 | } 17 | 18 | class BlocStateLoaded extends BlocState { 19 | var data; 20 | BlocStateLoaded({@required this.data}); 21 | 22 | @override 23 | String toString() => 'BlocStateLoaded'; 24 | } 25 | 26 | class BlocStateError extends BlocState { 27 | final Error error; 28 | BlocStateError({@required this.error}); 29 | 30 | @override 31 | String toString() => 'RelatedError'; 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/models/movie_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | import 'movie.dart'; 4 | 5 | part 'movie_list.g.dart'; 6 | 7 | @JsonSerializable() 8 | class MovieList extends Equatable { 9 | final String id; 10 | final String type; 11 | final String title; 12 | final String description; 13 | final String slug; 14 | final String thumbnail; 15 | final List children; 16 | 17 | @JsonKey(ignore: true) 18 | List childrenMovies; 19 | 20 | factory MovieList.fromJson(Map json) => 21 | _$MovieListFromJson(json); 22 | 23 | Map toJson() => _$MovieListToJson(this); 24 | 25 | MovieList(this.title, this.description, this.id, this.slug, this.thumbnail, 26 | this.type, this.children); 27 | } 28 | -------------------------------------------------------------------------------- /ios/Flutter/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'Flutter' 7 | s.version = '1.0.0' 8 | s.summary = 'High-performance, high-fidelity mobile apps.' 9 | s.description = <<-DESC 10 | Flutter provides an easy and productive way to build and deploy high-performance mobile apps for Android and iOS. 11 | DESC 12 | s.homepage = 'https://flutter.io' 13 | s.license = { :type => 'MIT' } 14 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 15 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 16 | s.ios.deployment_target = '8.0' 17 | s.vendored_frameworks = 'Flutter.framework' 18 | end 19 | -------------------------------------------------------------------------------- /lib/src/blocs/search_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:flutter_tubi/src/repositories/http_client.dart'; 3 | 4 | import './bloc_event.dart'; 5 | import './bloc_state.dart'; 6 | 7 | class SearchBloc extends Bloc { 8 | final httpClient = ApiClient(); 9 | String key; 10 | 11 | @override 12 | Stream mapEventToState( 13 | BlocState currentState, 14 | event, 15 | ) async* { 16 | if (event is BlocEventFetch) { 17 | yield BlocStateLoading(); 18 | 19 | try { 20 | final movies = await httpClient.movieSearch(key: key); 21 | yield BlocStateLoaded(data: movies); 22 | } catch (e) { 23 | print(e); 24 | yield BlocStateError(error: e); 25 | } 26 | } 27 | } 28 | 29 | @override 30 | BlocState get initialState => BlocStateUninitialized(); 31 | } 32 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/blocs/home/home_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter_tubi/src/models/models.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | abstract class HomeState extends Equatable { 6 | HomeState([List props = const []]) : super(props); 7 | } 8 | 9 | class HomeUninitialized extends HomeState { 10 | @override 11 | String toString() => 'HomeUninitialized'; 12 | } 13 | 14 | class HomeLoading extends HomeState { 15 | @override 16 | String toString() => 'HomeLoading'; 17 | } 18 | 19 | class HomeLoaded extends HomeState { 20 | final HomePageModel model; 21 | 22 | HomeLoaded({this.model}) : super([model]); 23 | 24 | @override 25 | String toString() => 'HomeLoaded { data: $model }'; 26 | } 27 | 28 | class HomeError extends HomeState { 29 | final Error error; 30 | HomeError({@required this.error}); 31 | 32 | @override 33 | String toString() => 'HomeError'; 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/models/video_resource.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | 4 | part 'video_resource.g.dart'; 5 | 6 | @JsonSerializable() 7 | class VideoResource extends Equatable { 8 | final VideoResourceManifest manifest; 9 | final String type; 10 | 11 | VideoResource(this.manifest, this.type); 12 | 13 | factory VideoResource.fromJson(Map json) => 14 | _$VideoResourceFromJson(json); 15 | 16 | Map toJson() => _$VideoResourceToJson(this); 17 | } 18 | 19 | @JsonSerializable() 20 | class VideoResourceManifest { 21 | final int duration; 22 | final String url; 23 | 24 | VideoResourceManifest(this.duration, this.url); 25 | 26 | factory VideoResourceManifest.fromJson(Map json) => 27 | _$VideoResourceManifestFromJson(json); 28 | 29 | Map toJson() => _$VideoResourceManifestToJson(this); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/blocs/related_movies/related_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter_tubi/src/models/models.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | abstract class RelatedState extends Equatable { 6 | RelatedState([List props = const []]) : super(props); 7 | } 8 | 9 | class RelatedUninitialized extends RelatedState { 10 | @override 11 | String toString() => 'RelatedUninitialized'; 12 | } 13 | 14 | class RelatedLoading extends RelatedState { 15 | @override 16 | String toString() => 'RelatedLoading'; 17 | } 18 | 19 | class RelatedLoaded extends RelatedState { 20 | final List movies; 21 | 22 | RelatedLoaded({this.movies}) : super([movies]); 23 | 24 | @override 25 | String toString() => 'RelatedLoaded { data: $movies }'; 26 | } 27 | 28 | class RelatedError extends RelatedState { 29 | final Error error; 30 | RelatedError({@required this.error}); 31 | 32 | @override 33 | String toString() => 'RelatedError'; 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/blocs/movie_details_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:flutter_tubi/src/repositories/http_client.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | import './bloc_event.dart'; 6 | import './bloc_state.dart'; 7 | 8 | class MovieDetailsBloc extends Bloc { 9 | final httpClient = ApiClient(); 10 | final String contentId; 11 | 12 | MovieDetailsBloc({@required this.contentId}); 13 | 14 | @override 15 | Stream mapEventToState( 16 | BlocState currentState, 17 | event, 18 | ) async* { 19 | if (event is BlocEventFetch) { 20 | yield BlocStateLoading(); 21 | 22 | try { 23 | final movie = await httpClient.fetchMovieDetails(contentId: contentId); 24 | yield BlocStateLoaded(data: movie); 25 | } catch (e) { 26 | print(e); 27 | yield BlocStateError(error: e); 28 | } 29 | } 30 | } 31 | 32 | @override 33 | BlocState get initialState => BlocStateUninitialized(); 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/blocs/home/home_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | import './home_event.dart'; 5 | import './home_state.dart'; 6 | import 'package:flutter_tubi/src/repositories/repositories.dart'; 7 | import 'package:flutter_tubi/src/models/models.dart'; 8 | 9 | class HomeBloc extends Bloc { 10 | @override 11 | HomeState get initialState => HomeUninitialized(); 12 | 13 | HomeBloc({@required this.homeRepository}) : assert(homeRepository != null); 14 | 15 | final HomePageRepository homeRepository; 16 | 17 | @override 18 | Stream mapEventToState( 19 | HomeState currentState, 20 | HomeEvent event, 21 | ) async* { 22 | if (event is FetchHomePage) { 23 | yield HomeLoading(); 24 | 25 | try { 26 | final HomePageModel homeModel = await homeRepository.getHomePageData(); 27 | yield HomeLoaded(model: homeModel); 28 | } catch (e) { 29 | print(e); 30 | yield HomeError(error: e); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/models/movie_list.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'movie_list.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | MovieList _$MovieListFromJson(Map json) { 10 | return MovieList( 11 | json['title'] as String, 12 | json['description'] as String, 13 | json['id'] as String, 14 | json['slug'] as String, 15 | json['thumbnail'] as String, 16 | json['type'] as String, 17 | (json['children'] as List)?.map((e) => e as String)?.toList()); 18 | } 19 | 20 | Map _$MovieListToJson(MovieList instance) => { 21 | 'id': instance.id, 22 | 'type': instance.type, 23 | 'title': instance.title, 24 | 'description': instance.description, 25 | 'slug': instance.slug, 26 | 'thumbnail': instance.thumbnail, 27 | 'children': instance.children 28 | }; 29 | -------------------------------------------------------------------------------- /lib/src/blocs/related_movies/related_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | import 'package:flutter_tubi/src/repositories/repositories.dart'; 5 | import './related_event.dart'; 6 | import './related_state.dart'; 7 | 8 | class RelatedBloc extends Bloc { 9 | @override 10 | RelatedState get initialState => RelatedUninitialized(); 11 | 12 | RelatedBloc({@required this.repository, @required this.id}) : assert(repository != null); 13 | 14 | final RelatedRepository repository; 15 | final String id; 16 | 17 | @override 18 | Stream mapEventToState( 19 | RelatedState currentState, 20 | event, 21 | ) async* { 22 | if (event is FetchRelatedMoviesEvent) { 23 | yield RelatedLoading(); 24 | 25 | try { 26 | final movies = await repository.getRelatedMovies(id: id); 27 | yield RelatedLoaded(movies: movies); 28 | } catch (e) { 29 | print(e); 30 | yield RelatedError(error: e); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/models/video_resource.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'video_resource.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | VideoResource _$VideoResourceFromJson(Map json) { 10 | return VideoResource( 11 | json['manifest'] == null 12 | ? null 13 | : VideoResourceManifest.fromJson( 14 | json['manifest'] as Map), 15 | json['type'] as String); 16 | } 17 | 18 | Map _$VideoResourceToJson(VideoResource instance) => 19 | {'manifest': instance.manifest, 'type': instance.type}; 20 | 21 | VideoResourceManifest _$VideoResourceManifestFromJson( 22 | Map json) { 23 | return VideoResourceManifest(json['duration'] as int, json['url'] as String); 24 | } 25 | 26 | Map _$VideoResourceManifestToJson( 27 | VideoResourceManifest instance) => 28 | {'duration': instance.duration, 'url': instance.url}; 29 | -------------------------------------------------------------------------------- /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/path_provider-0.5.0+1/","dependencies":[]},{"name":"sqflite","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.2.1/","dependencies":[]},{"name":"video_player","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/video_player-0.10.8+1/","dependencies":[]}],"android":[{"name":"path_provider","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/path_provider-0.5.0+1/","dependencies":[]},{"name":"sqflite","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.2.1/","dependencies":[]},{"name":"video_player","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/video_player-0.10.8+1/","dependencies":[]}],"macos":[{"name":"sqflite","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/sqflite-1.2.1/","dependencies":[]}],"linux":[],"windows":[],"web":[{"name":"video_player_web","path":"/Users/weizhi/.pub-cache/hosted/pub.flutter-io.cn/video_player_web-0.1.2+1/","dependencies":[]}]},"dependencyGraph":[{"name":"path_provider","dependencies":[]},{"name":"sqflite","dependencies":[]},{"name":"video_player","dependencies":["video_player_web"]},{"name":"video_player_web","dependencies":[]}],"date_created":"2020-02-29 11:45:55.820934","version":"1.15.4-pre.235"} -------------------------------------------------------------------------------- /lib/src/models/home.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'movie.dart'; 3 | import 'movie_list.dart'; 4 | 5 | class HomePageModel extends Equatable { 6 | final Map movies; 7 | final List movieLists; 8 | 9 | HomePageModel(this.movies, this.movieLists); 10 | 11 | static HomePageModel fromJson(Map json) { 12 | var moviesJson = (json['contents'] as Map); 13 | var movies = Map(); 14 | moviesJson.forEach((k, v) { 15 | movies[k] = Movie.fromJson(v); 16 | }); 17 | 18 | var containersJson = json['containers'] as List; 19 | var movieLists = containersJson.map((json) { 20 | var movieList = MovieList.fromJson(json); 21 | movieList.childrenMovies = movieList.children.map((id) => movies[id]).toList(); 22 | return movieList; 23 | }).toList(); 24 | 25 | return HomePageModel(movies, movieLists); 26 | } 27 | 28 | @override 29 | String toString() { 30 | return 'HomePageModel: ${movies.length} movies.'; 31 | } 32 | 33 | // Helpers 34 | 35 | List getFeaturedMovies() { 36 | return movieLists 37 | .firstWhere((list) => list.id == "featured") 38 | .childrenMovies; 39 | } 40 | 41 | List movieListExcludeFeatured() { 42 | return movieLists.where((list) => list.id != 'featured').toList(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_tubi 2 | 3 | A movie app building with Flutter for fun. 4 | 5 | 6 | Some screenshots 7 | 8 | iOS: 9 | 10 |
11 | home page 12 | movie details 13 | search 14 | video player 15 |
16 | 17 | 18 | Andoird: 19 | 20 |
21 | home page 22 | movie details 23 | search 24 | video player 25 |
26 | -------------------------------------------------------------------------------- /.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 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - path_provider (0.0.1): 7 | - Flutter 8 | - sqflite (0.0.1): 9 | - Flutter 10 | - FMDB (~> 2.7.2) 11 | - video_player (0.0.1): 12 | - Flutter 13 | - video_player_web (0.0.1): 14 | - Flutter 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `.symlinks/flutter/ios`) 18 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 19 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 20 | - video_player (from `.symlinks/plugins/video_player/ios`) 21 | - video_player_web (from `.symlinks/plugins/video_player_web/ios`) 22 | 23 | SPEC REPOS: 24 | https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: 25 | - FMDB 26 | 27 | EXTERNAL SOURCES: 28 | Flutter: 29 | :path: ".symlinks/flutter/ios" 30 | path_provider: 31 | :path: ".symlinks/plugins/path_provider/ios" 32 | sqflite: 33 | :path: ".symlinks/plugins/sqflite/ios" 34 | video_player: 35 | :path: ".symlinks/plugins/video_player/ios" 36 | video_player_web: 37 | :path: ".symlinks/plugins/video_player_web/ios" 38 | 39 | SPEC CHECKSUMS: 40 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 41 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 42 | path_provider: f96fff6166a8867510d2c25fdcc346327cc4b259 43 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 44 | video_player: 69c5f029fac4ffe4fc8a85ea7f7b793709661549 45 | video_player_web: da8cadb8274ed4f8dbee8d7171b420dedd437ce7 46 | 47 | PODFILE CHECKSUM: b83f2dc49df5eea17da2572f6e55ee2e4ce38bad 48 | 49 | COCOAPODS: 1.8.4 50 | -------------------------------------------------------------------------------- /lib/src/bottom_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BottomNavigation extends StatelessWidget { 4 | final int currentTab; 5 | final ValueChanged onSelectedTab; 6 | 7 | BottomNavigation({Key key, @required this.currentTab, this.onSelectedTab}) 8 | : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Theme( 13 | data: Theme.of(context).copyWith(canvasColor: Color(0xff26262d)), 14 | child: BottomNavigationBar( 15 | type: BottomNavigationBarType.fixed, 16 | items: [_makeItem(0), _makeItem(1), _makeItem(2)], 17 | onTap: (index) => onSelectedTab(index))); 18 | } 19 | 20 | BottomNavigationBarItem _makeItem(int index) { 21 | const images = [ 22 | "assets/icons/home_normal.png", 23 | "assets/icons/browse_normal.png", 24 | "assets/icons/search_normal.png" 25 | ]; 26 | const selectedImages = [ 27 | "assets/icons/home_selected.png", 28 | "assets/icons/browse_selected.png", 29 | "assets/icons/search_selected.png" 30 | ]; 31 | const titles = ["Home", "Browse", "Search"]; 32 | var image = (index == currentTab ? selectedImages : images)[index]; 33 | 34 | var selected = index == currentTab; 35 | var textColor = selected ? Color(0xffee5c32) : Colors.white; 36 | 37 | return BottomNavigationBarItem( 38 | icon: Container( 39 | child: Image.asset(image, color: selected ? textColor : null), 40 | width: 26, 41 | height: 26), 42 | title: Text( 43 | titles[index], 44 | style: TextStyle(color: textColor, fontSize: 12), 45 | )); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/models/movie.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_tubi/src/models/video_resource.dart'; 2 | import 'package:json_annotation/json_annotation.dart'; 3 | import 'package:equatable/equatable.dart'; 4 | 5 | part 'movie.g.dart'; 6 | 7 | @JsonSerializable() 8 | class Movie extends Equatable { 9 | final String id; 10 | final String type; 11 | final String title; 12 | final int duration; 13 | final String description; 14 | final int year; 15 | 16 | @JsonKey(name: "has_trailer") 17 | final bool hasTrailer; 18 | 19 | @JsonKey(name: "publisher_id") 20 | final String publisherId; 21 | 22 | @JsonKey(name: "has_subtitle") 23 | final bool hasSubtitle; 24 | 25 | @JsonKey(name: "import_id") 26 | final String importId; 27 | 28 | List> ratings; 29 | List actors; 30 | List directors; 31 | List tags; 32 | 33 | @JsonKey(name: 'video_resources', defaultValue: []) 34 | List videoResources; 35 | 36 | final List thumbnails; 37 | final List posterarts; 38 | final List backgrounds; 39 | 40 | @JsonKey(name: "hero_images") 41 | final List heroImages; 42 | @JsonKey(name: "landscape_images") 43 | final List landscapeImages; 44 | 45 | factory Movie.fromJson(Map json) => _$MovieFromJson(json); 46 | 47 | Map toJson() => _$MovieToJson(this); 48 | 49 | Movie( 50 | this.id, 51 | this.description, 52 | this.heroImages, 53 | this.thumbnails, 54 | this.posterarts, 55 | this.title, 56 | this.type, 57 | this.duration, 58 | this.year, 59 | this.hasTrailer, 60 | this.publisherId, 61 | this.hasSubtitle, 62 | this.importId, 63 | this.ratings, 64 | this.backgrounds, 65 | this.landscapeImages, 66 | this.videoResources); 67 | } 68 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_tubi 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIStatusBarStyle 35 | UIStatusBarStyleLightContent 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeRight 40 | UIInterfaceOrientationLandscapeLeft 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/src/ui/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_tubi/src/blocs/home/bloc.dart'; 4 | 5 | import 'home_list_view.dart'; 6 | 7 | class HomePage extends StatefulWidget { 8 | final HomeBloc homeBloc; 9 | 10 | const HomePage({Key key, @required this.homeBloc}) : super(key: key); 11 | @override 12 | _HomePageState createState() => _HomePageState(homeBloc: homeBloc); 13 | } 14 | 15 | class _HomePageState extends State { 16 | final HomeBloc homeBloc; 17 | 18 | _HomePageState({@required this.homeBloc}); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return BlocBuilder( 23 | bloc: homeBloc, 24 | builder: (_, HomeState state) { 25 | if (state is HomeLoaded) { 26 | return HomeListView(homePageModel: state.model); 27 | } 28 | if (state is HomeError) { 29 | return AlertDialog( 30 | title: Text('Error'), 31 | content: SingleChildScrollView( 32 | child: ListBody( 33 | children: [ 34 | Text( 35 | 'Looks like the system is unavaliable, please try again'), 36 | ], 37 | ), 38 | ), 39 | actions: [ 40 | FlatButton( 41 | child: Text('Cancel'), 42 | onPressed: () { 43 | Navigator.of(context).pop(); 44 | }, 45 | ), 46 | FlatButton( 47 | child: Text('Try Again'), 48 | onPressed: () { 49 | Navigator.of(context).pop(); 50 | homeBloc.dispatch(FetchHomePage()); 51 | }, 52 | ) 53 | ], 54 | ); 55 | } 56 | return Center(child: CircularProgressIndicator()); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 16 | 24 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.flutter_tubi" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/ui/video_player/video_paly_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:video_player/video_player.dart'; 3 | 4 | typedef VideoPlayDidButtonAction = void Function(); 5 | 6 | class VideoPlayControls extends StatefulWidget { 7 | final VideoPlayerController controller; 8 | final VoidCallback togglePlayAction; 9 | final VoidCallback backwardAction; 10 | final VoidCallback forwardAction; 11 | 12 | const VideoPlayControls( 13 | {Key key, 14 | @required this.controller, 15 | this.togglePlayAction, 16 | this.backwardAction, 17 | this.forwardAction}) 18 | : super(key: key); 19 | 20 | @override 21 | State createState() { 22 | return _VideoPlayControlsState(); 23 | } 24 | } 25 | 26 | class _VideoPlayControlsState extends State { 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | final playing = widget.controller.value.isPlaying ?? false; 31 | return SizedBox.expand( 32 | child: 33 | Row(mainAxisAlignment: MainAxisAlignment.center, children: [ 34 | FlatButton( 35 | onPressed: () { 36 | widget.backwardAction(); 37 | }, 38 | child: SizedBox( 39 | width: 30, 40 | height: 30, 41 | child: Image.asset( 42 | "assets/icons/rew_15_normal.png", 43 | fit: BoxFit.fill, 44 | ), 45 | ), 46 | ), 47 | FlatButton( 48 | onPressed: () { 49 | widget.togglePlayAction(); 50 | }, 51 | child: SizedBox( 52 | width: 50, 53 | height: 50, 54 | child: Image.asset( 55 | "assets/icons/${playing ? "pause_normal.png" : "play_large_normal.png"}", 56 | fit: BoxFit.fill, 57 | ), 58 | ), 59 | ), 60 | FlatButton( 61 | onPressed: () { 62 | widget.forwardAction(); 63 | }, 64 | child: SizedBox( 65 | width: 30, 66 | height: 30, 67 | child: Image.asset( 68 | "assets/icons/fwd_15_normal.png", 69 | fit: BoxFit.fill, 70 | ), 71 | ), 72 | ), 73 | ])); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/tab_navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_tubi/src/ui/category/movie_category.dart'; 4 | import 'package:flutter_tubi/src/ui/home/home_page.dart'; 5 | import 'package:flutter_tubi/src/ui/browse/browse_page.dart'; 6 | import 'package:flutter_tubi/src/ui/movie_page/movie_page.dart'; 7 | import 'package:flutter_tubi/src/ui/search/search_page.dart'; 8 | import 'package:flutter_tubi/src/blocs/home/bloc.dart'; 9 | 10 | class TabType { 11 | static const home = 0; 12 | static const browse = 1; 13 | static const search = 2; 14 | } 15 | 16 | class TabNavigatorRoutes { 17 | static const String root = '/'; 18 | static const String movieCategory = 'movieCategory'; 19 | } 20 | 21 | class TabNavigator extends StatelessWidget { 22 | final GlobalKey navigatorKey; 23 | final int currentTab; 24 | final HomeBloc homeBloc; 25 | 26 | TabNavigator( 27 | {Key key, 28 | @required this.navigatorKey, 29 | @required this.currentTab, 30 | @required this.homeBloc}) 31 | : super(key: key); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return Navigator( 36 | key: navigatorKey, 37 | initialRoute: TabNavigatorRoutes.root, 38 | onGenerateRoute: (routeSettings) { 39 | return _routeForRouteSettings(routeSettings: routeSettings); 40 | }); 41 | } 42 | 43 | PageRoute _routeForRouteSettings({@required RouteSettings routeSettings}) { 44 | return MaterialPageRoute(builder: (context) { 45 | if (routeSettings.name == TabNavigatorRoutes.root) { 46 | switch (currentTab) { 47 | case TabType.home: 48 | return HomePage(homeBloc: homeBloc); 49 | case TabType.browse: 50 | return BrowsePage(homeBloc: homeBloc); 51 | case TabType.search: 52 | return SearchPage(); 53 | default: 54 | break; 55 | } 56 | } 57 | if (routeSettings.name == MoviePageArguments.routeName) { 58 | final MoviePageArguments args = routeSettings.arguments; 59 | return MoviePage(movie: args.movie); 60 | } 61 | if (routeSettings.name == TabNavigatorRoutes.movieCategory) { 62 | final MovieCategoryPageArguments args = routeSettings.arguments; 63 | return MovieCategoryPage(movies: args.movies, title: args.title); 64 | } 65 | 66 | return null; 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' 2 | 3 | # Uncomment this line to define a global platform for your project 4 | # platform :ios, '9.0' 5 | 6 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 7 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 8 | 9 | project 'Runner', { 10 | 'Debug' => :debug, 11 | 'Profile' => :release, 12 | 'Release' => :release, 13 | } 14 | 15 | def parse_KV_file(file, separator='=') 16 | file_abs_path = File.expand_path(file) 17 | if !File.exists? file_abs_path 18 | return []; 19 | end 20 | pods_ary = [] 21 | skip_line_start_symbols = ["#", "/"] 22 | File.foreach(file_abs_path) { |line| 23 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 24 | plugin = line.split(pattern=separator) 25 | if plugin.length == 2 26 | podname = plugin[0].strip() 27 | path = plugin[1].strip() 28 | podpath = File.expand_path("#{path}", file_abs_path) 29 | pods_ary.push({:name => podname, :path => podpath}); 30 | else 31 | puts "Invalid plugin specification: #{line}" 32 | end 33 | } 34 | return pods_ary 35 | end 36 | 37 | target 'Runner' do 38 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 39 | # referring to absolute paths on developers' machines. 40 | system('rm -rf .symlinks') 41 | system('mkdir -p .symlinks/plugins') 42 | 43 | # Flutter Pods 44 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 45 | if generated_xcode_build_settings.empty? 46 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 47 | end 48 | generated_xcode_build_settings.map { |p| 49 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 50 | symlink = File.join('.symlinks', 'flutter') 51 | File.symlink(File.dirname(p[:path]), symlink) 52 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 53 | end 54 | } 55 | 56 | # Plugin Pods 57 | plugin_pods = parse_KV_file('../.flutter-plugins') 58 | plugin_pods.map { |p| 59 | symlink = File.join('.symlinks', 'plugins', p[:name]) 60 | File.symlink(p[:path], symlink) 61 | pod p[:name], :path => File.join(symlink, 'ios') 62 | } 63 | end 64 | 65 | post_install do |installer| 66 | installer.pods_project.targets.each do |target| 67 | target.build_configurations.each do |config| 68 | config.build_settings['ENABLE_BITCODE'] = 'NO' 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/src/ui/home/home_movie_scroll_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:meta/meta.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter_tubi/src/models/models.dart'; 5 | import '../movie_page/movie_page.dart'; 6 | 7 | class HomeMovieScrollRow extends StatelessWidget { 8 | final List movies; 9 | 10 | const HomeMovieScrollRow({@required Key key, @required this.movies}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | width: MediaQuery.of(context).size.width, 17 | child: ListView.builder( 18 | key: key, 19 | itemCount: movies.length, 20 | itemBuilder: (context, index) { 21 | var movie = movies[index]; 22 | return Padding( 23 | padding: EdgeInsets.only(right: 6.0), 24 | child: GestureDetector( 25 | child: _ScrollItemView(movie: movie), 26 | onTap: () { 27 | Navigator.pushNamed( 28 | context, MoviePageArguments.routeName, 29 | arguments: MoviePageArguments(movie)); 30 | })); 31 | }, 32 | padding: EdgeInsets.only(left: 16.0), 33 | scrollDirection: Axis.horizontal, 34 | physics: BouncingScrollPhysics())); 35 | } 36 | } 37 | 38 | class _ScrollItemView extends StatelessWidget { 39 | final Movie movie; 40 | 41 | const _ScrollItemView({Key key, @required this.movie}) : super(key: key); 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return SizedBox( 46 | width: 120, 47 | child: Column( 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | CachedNetworkImage( 51 | placeholder: (context, url) { 52 | return AspectRatio( 53 | aspectRatio: 0.68, 54 | child: Image.asset("assets/icons/placeholder.png", 55 | fit: BoxFit.cover)); 56 | }, 57 | imageUrl: movie.posterarts.first, 58 | fit: BoxFit.cover), 59 | SizedBox(height: 14), 60 | Text(movie.title, 61 | maxLines: 2, 62 | textAlign: TextAlign.left, 63 | style: TextStyle(fontSize: 14, color: Colors.white)) 64 | ])); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/models/movie.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'movie.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | Movie _$MovieFromJson(Map json) { 10 | return Movie( 11 | json['id'] as String, 12 | json['description'] as String, 13 | (json['hero_images'] as List)?.map((e) => e as String)?.toList(), 14 | (json['thumbnails'] as List)?.map((e) => e as String)?.toList(), 15 | (json['posterarts'] as List)?.map((e) => e as String)?.toList(), 16 | json['title'] as String, 17 | json['type'] as String, 18 | json['duration'] as int, 19 | json['year'] as int, 20 | json['has_trailer'] as bool, 21 | json['publisher_id'] as String, 22 | json['has_subtitle'] as bool, 23 | json['import_id'] as String, 24 | (json['ratings'] as List) 25 | ?.map((e) => (e as Map)?.map( 26 | (k, e) => MapEntry(k, e as String), 27 | )) 28 | ?.toList(), 29 | (json['backgrounds'] as List)?.map((e) => e as String)?.toList(), 30 | (json['landscape_images'] as List)?.map((e) => e as String)?.toList(), 31 | (json['video_resources'] as List) 32 | ?.map((e) => e == null 33 | ? null 34 | : VideoResource.fromJson(e as Map)) 35 | ?.toList() ?? 36 | []) 37 | ..actors = (json['actors'] as List)?.map((e) => e as String)?.toList() 38 | ..directors = (json['directors'] as List)?.map((e) => e as String)?.toList() 39 | ..tags = (json['tags'] as List)?.map((e) => e as String)?.toList(); 40 | } 41 | 42 | Map _$MovieToJson(Movie instance) => { 43 | 'id': instance.id, 44 | 'type': instance.type, 45 | 'title': instance.title, 46 | 'duration': instance.duration, 47 | 'description': instance.description, 48 | 'year': instance.year, 49 | 'has_trailer': instance.hasTrailer, 50 | 'publisher_id': instance.publisherId, 51 | 'has_subtitle': instance.hasSubtitle, 52 | 'import_id': instance.importId, 53 | 'ratings': instance.ratings, 54 | 'actors': instance.actors, 55 | 'directors': instance.directors, 56 | 'tags': instance.tags, 57 | 'video_resources': instance.videoResources, 58 | 'thumbnails': instance.thumbnails, 59 | 'posterarts': instance.posterarts, 60 | 'backgrounds': instance.backgrounds, 61 | 'hero_images': instance.heroImages, 62 | 'landscape_images': instance.landscapeImages 63 | }; 64 | -------------------------------------------------------------------------------- /lib/src/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_tubi/src/blocs/home/bloc.dart'; 3 | import 'package:flutter_tubi/src/repositories/repositories.dart'; 4 | import 'package:flutter_tubi/src/ui/video_player/video_player.dart'; 5 | import 'tab_navigator.dart'; 6 | import 'bottom_navigation.dart'; 7 | 8 | class App extends StatefulWidget { 9 | @override 10 | _AppState createState() => _AppState(); 11 | } 12 | 13 | class _AppState extends State { 14 | int _currentTab = TabType.home; 15 | final navigatorKey = GlobalKey(); 16 | HomeBloc _homeBloc; 17 | 18 | /// keys for each tab 19 | final Map> navigatorKeys = { 20 | TabType.home: GlobalKey(), 21 | TabType.browse: GlobalKey(), 22 | TabType.search: GlobalKey() 23 | }; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | HomePageRepository repo = HomePageRepository(); 29 | _homeBloc = HomeBloc(homeRepository: repo); 30 | _homeBloc.dispatch(FetchHomePage()); 31 | } 32 | 33 | void _tabBarItemOnTap(int index) { 34 | setState(() { 35 | this._currentTab = index; 36 | }); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return MaterialApp( 42 | darkTheme: ThemeData.dark(), 43 | theme: ThemeData( 44 | fontFamily: 'Vaud', 45 | brightness: Brightness.dark, 46 | primaryColor: Color(0xff26262d), 47 | backgroundColor: Color(0xff26262d)), 48 | home: Scaffold( 49 | backgroundColor: Color(0xff26262d), 50 | body: Stack( 51 | children: [ 52 | _buildOffstageNavigator(TabType.home), 53 | _buildOffstageNavigator(TabType.browse), 54 | _buildOffstageNavigator(TabType.search), 55 | ], 56 | ), 57 | bottomNavigationBar: BottomNavigation( 58 | currentTab: _currentTab, 59 | onSelectedTab: _tabBarItemOnTap, 60 | )), 61 | onGenerateRoute: (routeSettings) { 62 | final VideoPlayerPageArguments args = routeSettings.arguments; 63 | return MaterialPageRoute( 64 | fullscreenDialog: true, 65 | settings: routeSettings, 66 | builder: (context) { 67 | return VideoPlayerPage(movie: args.movie, url: args.url); 68 | }); 69 | }, 70 | ); 71 | } 72 | 73 | Widget _buildOffstageNavigator(int tab) { 74 | return Offstage( 75 | offstage: _currentTab != tab, 76 | child: TabNavigator( 77 | homeBloc: _homeBloc, 78 | navigatorKey: navigatorKeys[tab], 79 | currentTab: tab, 80 | )); 81 | } 82 | 83 | @override 84 | void dispose() { 85 | _homeBloc.dispose(); 86 | super.dispose(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/repositories/http_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:meta/meta.dart'; 4 | import 'package:http/http.dart' as http; 5 | 6 | import '../models/models.dart'; 7 | 8 | class ApiClient { 9 | static const baseUrl = 'https://uapi.adrise.tv'; 10 | final http.Client httpClient = http.Client(); 11 | Map _headers = { 12 | "Accept-Encoding": "deflate, gzip;q=1.0, *;q=0.5" 13 | }; 14 | 15 | Future fetchHomePage() async { 16 | final homeUrl = 17 | '$baseUrl/matrix/homescreen?app_id=tubitv&device_id=3B17D64C-8A40-4234-A80E-0C3020F621B7&expand=2&limit=40&page_enabled=false&platform=iphone&user_id=0'; 18 | final homeResponse = await this.httpClient.get(homeUrl, headers: _headers); 19 | 20 | if (homeResponse.statusCode != 200) { 21 | throw Exception('error getting home page data'); 22 | } 23 | 24 | final json = jsonDecode(homeResponse.body); 25 | return HomePageModel.fromJson(json); 26 | } 27 | 28 | Future> fetchRelatedMovies({@required String id}) async { 29 | final url = 30 | '$baseUrl/cms/content/$id/related?app_id=tubitv&device_id=3B17D64C-8A40-4234-A80E-0C3020F621B7&page_enabled=false&platform=iphone&user_id=0'; 31 | final response = await this.httpClient.get(url, headers: _headers); 32 | 33 | if (response.statusCode != 200) { 34 | throw Exception('error getting related movies'); 35 | } 36 | 37 | final movies = (jsonDecode(response.body) as List) 38 | .map((json) => Movie.fromJson(json)) 39 | .toList(); 40 | return movies; 41 | } 42 | 43 | Future fetchMovieDetails({@required String contentId}) async { 44 | final url = 45 | '$baseUrl/cms/content?app_id=tubitv&content_id=$contentId&device_id=3B17D64C-8A40-4234-A80E-0C3020F621B7&includeChannels=true&page_enabled=false&platform=iphone&user_id=1'; 46 | final response = await this.httpClient.get(url, headers: _headers); 47 | 48 | if (response.statusCode != 200) { 49 | throw Exception('error getting movie details'); 50 | } 51 | final json = jsonDecode(response.body); 52 | 53 | return Movie.fromJson(json); 54 | } 55 | 56 | Future> movieSearch({@required String key}) async { 57 | final url = 58 | '$baseUrl/cms/search?app_id=tubitv&categorize=true&device_id=3B17D64C-8A40-4234-A80E-0C3020F621B7&page_enabled=false&platform=iphone&search=$key&user_id=0'; 59 | final response = await this.httpClient.get(url, headers: _headers); 60 | 61 | if (response.statusCode != 200) { 62 | throw Exception('error searching movies'); 63 | } 64 | final json = jsonDecode(response.body); 65 | 66 | var moviesJson = (json['contents'] as Map); 67 | var movies = Map(); 68 | moviesJson.forEach((k, v) { 69 | movies[k] = Movie.fromJson(v); 70 | }); 71 | 72 | return movies.values.toList(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/src/ui/video_player/video_duration_toolbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:video_player/video_player.dart'; 3 | import 'share_data_widget.dart'; 4 | 5 | class VideoDurationToolBar extends StatefulWidget { 6 | final VideoPlayerController controller; 7 | 8 | const VideoDurationToolBar({Key key, @required this.controller}) 9 | : super(key: key); 10 | 11 | @override 12 | State createState() { 13 | return _VideoDurationToolBarState(); 14 | } 15 | } 16 | 17 | class _VideoDurationToolBarState extends State { 18 | VideoPlayerController get controller => widget.controller; 19 | VoidCallback listener; 20 | 21 | _VideoDurationToolBarState() { 22 | listener = () { 23 | if (mounted) { 24 | setState(() {}); 25 | } 26 | }; 27 | } 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | controller.addListener(listener); 33 | } 34 | 35 | @override 36 | void deactivate() { 37 | controller.removeListener(listener); 38 | super.deactivate(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | print("shared: "+ShareDataWidget.of(context).data.toString()); 44 | return Container( 45 | height: 30, 46 | child: 47 | Row(mainAxisAlignment: MainAxisAlignment.center, children: [ 48 | SizedBox( 49 | child: ConstrainedBox( 50 | constraints: BoxConstraints(minWidth: 120), 51 | child: Text(_position(value: ShareDataWidget.of(context).data), 52 | style: TextStyle( 53 | color: Colors.white, 54 | fontSize: 12, 55 | decoration: TextDecoration.none), 56 | textAlign: TextAlign.right))), 57 | Expanded( 58 | child: VideoProgressIndicator( 59 | widget.controller, 60 | allowScrubbing: true, 61 | padding: EdgeInsets.symmetric(horizontal: 20), 62 | )), 63 | SizedBox( 64 | child: ConstrainedBox( 65 | constraints: BoxConstraints(minWidth: 120), 66 | child: Text(_duration(value: ShareDataWidget.of(context).data), 67 | style: TextStyle( 68 | color: Colors.white, 69 | fontSize: 12, 70 | decoration: TextDecoration.none), 71 | textAlign: TextAlign.left))) 72 | ]), 73 | ); 74 | } 75 | 76 | String _duration({@required VideoPlayerValue value}) { 77 | final pos = value.position.inSeconds; 78 | final seconds = value.duration.inSeconds - pos; 79 | return "${seconds / 60 ~/ 60}:${(seconds / 60 % 60).toInt()}:${seconds % 60}"; 80 | } 81 | 82 | String _position({@required VideoPlayerValue value}) { 83 | final seconds = value.position.inSeconds; 84 | return "${seconds / 60 ~/ 60}:${(seconds / 60 % 60).toInt()}:${seconds % 60}"; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_tubi 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | http: ^0.12.0+1 21 | bloc: ^0.10.0 22 | flutter_bloc: ^0.8.0 23 | equatable: ^0.2.3 24 | json_serializable: ^2.0.3 25 | cached_network_image: ^2.0.0 26 | video_player: ^0.10.0+4 27 | flutter: 28 | sdk: flutter 29 | 30 | # The following adds the Cupertino Icons font to your application. 31 | # Use with the CupertinoIcons class for iOS style icons. 32 | cupertino_icons: ^0.1.2 33 | 34 | dev_dependencies: 35 | build_runner: ^1.2.8 36 | flutter_test: 37 | sdk: flutter 38 | 39 | 40 | # For information on the generic Dart part of this file, see the 41 | # following page: https://www.dartlang.org/tools/pub/pubspec 42 | 43 | # The following section is specific to Flutter. 44 | flutter: 45 | 46 | # The following line ensures that the Material Icons font is 47 | # included with your application, so that you can use the icons in 48 | # the material Icons class. 49 | uses-material-design: true 50 | 51 | assets: 52 | - assets/ 53 | - assets/icons/ 54 | 55 | # An image asset can refer to one or more resolution-specific "variants", see 56 | # https://flutter.io/assets-and-images/#resolution-aware. 57 | 58 | # For details regarding adding assets from package dependencies, see 59 | # https://flutter.io/assets-and-images/#from-packages 60 | 61 | # To add custom fonts to your application, add a fonts section here, 62 | # in this "flutter" section. Each entry in this list should have a 63 | # "family" key with the font family name, and a "fonts" key with a 64 | # list giving the asset and other descriptors for the font. For 65 | # example: 66 | fonts: 67 | - family: Vaud 68 | fonts: 69 | - asset: assets/fonts/Vaud-Bold.ttf 70 | - asset: assets/fonts/Vaud-Medium.otf 71 | - asset: assets/fonts/Vaud-SemiBold.otf 72 | - asset: assets/fonts/Vaud-Thin.otf 73 | - asset: assets/fonts/Vaud-Ultra.otf 74 | - asset: assets/fonts/Vaud-UltraLight.otf 75 | - asset: assets/fonts/Vaud.otf 76 | 77 | # 78 | # For details regarding fonts from package dependencies, 79 | # see https://flutter.io/custom-fonts/#from-packages 80 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/src/ui/category/movie_category.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_tubi/src/models/models.dart'; 4 | import 'package:flutter_tubi/src/ui/movie_page/movie_page.dart'; 5 | 6 | class MovieCategoryPageArguments { 7 | static final routeName = "movieCategory"; 8 | final String title; 9 | final List movies; 10 | 11 | MovieCategoryPageArguments({@required this.movies, @required this.title}); 12 | } 13 | 14 | class MovieCategoryPage extends StatefulWidget { 15 | final List movies; 16 | final String title; 17 | final bool showAppBar; 18 | 19 | const MovieCategoryPage({Key key, @required this.movies, @required this.title, this.showAppBar}) 20 | : super(key: key); 21 | 22 | @override 23 | State createState() { 24 | return _MovieCategoryPageState(); 25 | } 26 | } 27 | 28 | class _MovieCategoryPageState extends State { 29 | List get movies => widget.movies; 30 | String get title => widget.title; 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | final itemWidth = (MediaQuery.of(context).size.width - 2 * 16 - 8 * 2) / 3.0; 35 | final itemHeight = itemWidth * 1.46 + 80.0; 36 | final showAppBar = widget.showAppBar ?? true; 37 | return Container( 38 | color: Color(0xff26262d), 39 | child: Container( 40 | child: CustomScrollView(slivers: [ 41 | showAppBar ? SliverAppBar( 42 | title: Text(title), 43 | pinned: true, 44 | backgroundColor: Color(0xff26262d)) : SliverAppBar(), 45 | SliverPadding( 46 | padding: EdgeInsets.symmetric(horizontal: 16), 47 | sliver: SliverGrid( 48 | delegate: SliverChildBuilderDelegate( 49 | (BuildContext contet, int index) { 50 | var movie = movies[index]; 51 | return _buildGridItem(context, movie); 52 | }, childCount: movies.length), 53 | gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( 54 | maxCrossAxisExtent: itemWidth, 55 | mainAxisSpacing: 4, 56 | crossAxisSpacing: 8, 57 | childAspectRatio: itemWidth / itemHeight), 58 | )) 59 | ]), 60 | )); 61 | } 62 | 63 | Widget _buildGridItem(BuildContext context, Movie movie) { 64 | return InkWell( 65 | onTap: () { 66 | Navigator.pushNamed(context, MoviePageArguments.routeName, 67 | arguments: MoviePageArguments(movie)); 68 | }, 69 | child: SizedBox( 70 | child: Column( 71 | crossAxisAlignment: CrossAxisAlignment.start, 72 | children: [ 73 | AspectRatio( 74 | aspectRatio: 0.68, 75 | child: CachedNetworkImage( 76 | placeholder: (context, url) { 77 | return Image.asset("assets/icons/placeholder.png"); 78 | }, 79 | imageUrl: movie.posterarts.first, 80 | fit: BoxFit.cover)), 81 | SizedBox(height: 14), 82 | Text(movie.title, 83 | maxLines: 2, 84 | textAlign: TextAlign.left, 85 | style: TextStyle(fontSize: 14, color: Colors.white)) 86 | ]))); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/ui/home/home_list_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_tubi/src/tab_navigator.dart'; 3 | import 'package:flutter_tubi/src/ui/category/movie_category.dart'; 4 | import 'package:meta/meta.dart'; 5 | 6 | import 'package:flutter_tubi/src/models/models.dart'; 7 | import 'home_featured_row.dart'; 8 | import 'home_movie_scroll_row.dart'; 9 | 10 | class HomeListView extends StatelessWidget { 11 | final HomePageModel homePageModel; 12 | const HomeListView({Key key, @required this.homePageModel}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | List allMovieList = 17 | homePageModel.movieListExcludeFeatured() ?? []; 18 | var featuredMovies = homePageModel.getFeaturedMovies() ?? []; 19 | 20 | return Container( 21 | color: Color(0xff26262d), 22 | child: CustomScrollView(slivers: [ 23 | _buildAppBar( 24 | featuredMovies: featuredMovies, 25 | height: MediaQuery.of(context).size.width * 0.6), 26 | SliverFixedExtentList( 27 | delegate: 28 | SliverChildBuilderDelegate((BuildContext context, int index) { 29 | return _buildRowView(allMovieList[index]); 30 | }, childCount: allMovieList.length), 31 | itemExtent: 303), 32 | ])); 33 | } 34 | 35 | Widget _buildRowView(MovieList movieList) { 36 | return Column(children: [ 37 | Padding( 38 | padding: EdgeInsets.fromLTRB(16, 0, 0, 0), 39 | child: _ListSectionTitleView(movieList: movieList)), 40 | Container( 41 | height: 250, 42 | child: HomeMovieScrollRow( 43 | key: PageStorageKey(movieList.id), 44 | movies: movieList.childrenMovies, 45 | )), 46 | Container( 47 | height: 1, 48 | child: Divider(color: Colors.white24), 49 | ) 50 | ]); 51 | } 52 | 53 | Widget _buildAppBar({List featuredMovies, double height}) { 54 | return SliverAppBar( 55 | title: Image.asset("assets/icons/icon_nav_logo.png"), 56 | pinned: true, 57 | backgroundColor: Color(0xff26262d), 58 | expandedHeight: height, 59 | flexibleSpace: 60 | FlexibleSpaceBar(background: HomeFeaturedRow(movies: featuredMovies)), 61 | ); 62 | } 63 | } 64 | 65 | class _ListSectionTitleView extends StatelessWidget { 66 | final MovieList movieList; 67 | 68 | const _ListSectionTitleView({Key key, @required this.movieList}) 69 | : super(key: key); 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | var button = MaterialButton( 74 | minWidth: 20, 75 | child: Image.asset("assets/icons/iconOverflow.png"), 76 | onPressed: () { 77 | Navigator.pushNamed(context, TabNavigatorRoutes.movieCategory, 78 | arguments: MovieCategoryPageArguments( 79 | movies: movieList.childrenMovies, title: movieList.title)); 80 | }, 81 | ); 82 | 83 | return SizedBox( 84 | width: MediaQuery.of(context).size.width, 85 | height: 52, 86 | child: Stack( 87 | children: [ 88 | Align( 89 | alignment: Alignment.centerLeft, 90 | child: Text(movieList.title, 91 | style: TextStyle( 92 | color: Colors.white, 93 | fontSize: 14, 94 | fontWeight: FontWeight.bold))), 95 | Align(alignment: Alignment.centerRight, child: button) 96 | ], 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/src/ui/browse/browse_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_tubi/src/blocs/home/bloc.dart'; 5 | import 'package:flutter_tubi/src/models/models.dart'; 6 | import 'package:flutter_tubi/src/ui/category/movie_category.dart'; 7 | 8 | class BrowsePage extends StatefulWidget { 9 | final HomeBloc homeBloc; 10 | const BrowsePage({Key key, @required this.homeBloc}) : super(key: key); 11 | 12 | @override 13 | State createState() { 14 | return _BrowsePageState(homeBloc: homeBloc); 15 | } 16 | } 17 | 18 | class _BrowsePageState extends State { 19 | final HomeBloc homeBloc; 20 | 21 | _BrowsePageState({@required this.homeBloc}); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return BlocBuilder( 26 | bloc: homeBloc, 27 | builder: (ctx, HomeState state) { 28 | if (state is HomeLoaded) { 29 | return _buildListView(model: state.model, context: ctx); 30 | } 31 | if (state is HomeError) { 32 | return Center(child: Text(state.error.toString())); 33 | } 34 | return Center(child: CircularProgressIndicator()); 35 | }); 36 | } 37 | 38 | Widget _buildListView({HomePageModel model, BuildContext context}) { 39 | var movieLists = model.movieLists 40 | .where((list) => list.type != 'continue_watching') 41 | .toList(); 42 | movieLists.sort((a, b) { 43 | return a.title.compareTo(b.title); 44 | }); 45 | return CustomScrollView(slivers: [ 46 | SliverAppBar( 47 | title: Text("Browse"), 48 | pinned: true, 49 | backgroundColor: Color(0xff26262d)), 50 | SliverFixedExtentList( 51 | delegate: 52 | SliverChildBuilderDelegate((BuildContext context, int index) { 53 | return _buildListItem(model: movieLists[index], context: context); 54 | }, childCount: movieLists.length), 55 | itemExtent: 70), 56 | ]); 57 | } 58 | 59 | Widget _buildListItem({MovieList model, BuildContext context}) { 60 | return InkWell( 61 | onTap: () { 62 | Navigator.pushNamed(context, MovieCategoryPageArguments.routeName, 63 | arguments: MovieCategoryPageArguments( 64 | movies: model.childrenMovies, title: model.title)); 65 | }, 66 | child: Card( 67 | shape: RoundedRectangleBorder( 68 | borderRadius: BorderRadius.circular(3.0)), 69 | margin: EdgeInsets.fromLTRB(16, 4, 16, 4), 70 | child: Container( 71 | child: Stack(children: [ 72 | Align( 73 | alignment: Alignment.centerRight, 74 | child: Container( 75 | width: 180, 76 | child: CachedNetworkImage( 77 | imageUrl: model.thumbnail ?? "", fit: BoxFit.cover))), 78 | Align( 79 | alignment: Alignment.center, 80 | child: Container( 81 | decoration: BoxDecoration( 82 | gradient: LinearGradient( 83 | begin: Alignment.centerLeft, 84 | end: Alignment.centerRight, 85 | colors: [Colors.transparent, Colors.grey])), 86 | )), 87 | Container( 88 | padding: EdgeInsets.only(left: 24), 89 | child: Align( 90 | alignment: Alignment.centerLeft, 91 | child: Text( 92 | model.title, 93 | style: TextStyle( 94 | fontSize: 14, fontWeight: FontWeight.bold), 95 | ))) 96 | ])))); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/ui/home/home_featured_row.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_tubi/src/ui/movie_page/movie_page.dart'; 3 | import 'package:meta/meta.dart'; 4 | import 'package:cached_network_image/cached_network_image.dart'; 5 | import 'package:flutter_tubi/src/models/models.dart'; 6 | 7 | class HomeFeaturedRow extends StatefulWidget { 8 | final List movies; 9 | 10 | const HomeFeaturedRow({Key key, @required this.movies}) : super(key: key); 11 | 12 | @override 13 | _HomeFeaturedRowState createState() => _HomeFeaturedRowState(this.movies); 14 | } 15 | 16 | class _HomeFeaturedRowState extends State 17 | with AutomaticKeepAliveClientMixin { 18 | final List movies; 19 | int _currentPage = 0; 20 | 21 | _HomeFeaturedRowState(this.movies); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | super.build(context); 26 | var pageController = PageController(initialPage: _currentPage); 27 | 28 | var pageView = PageView.builder( 29 | key: PageStorageKey(context), 30 | itemCount: movies.length, 31 | controller: pageController, 32 | itemBuilder: (context, index) { 33 | return _PageItemView( 34 | movie: movies[index], totalPage: movies.length, currentPage: index); 35 | }, 36 | onPageChanged: (page) { 37 | setState(() { 38 | this._currentPage = page; 39 | }); 40 | }, 41 | ); 42 | 43 | return Container(child: pageView); 44 | } 45 | 46 | @override 47 | bool get wantKeepAlive => true; 48 | } 49 | 50 | class _PageItemView extends StatelessWidget { 51 | final Movie movie; 52 | final int totalPage; 53 | final int currentPage; 54 | 55 | const _PageItemView( 56 | {Key key, 57 | @required this.movie, 58 | @required this.totalPage, 59 | @required this.currentPage}) 60 | : super(key: key); 61 | 62 | @override 63 | Widget build(BuildContext context) { 64 | return GestureDetector( 65 | onTap: () { 66 | Navigator.pushNamed(context, MoviePageArguments.routeName, 67 | arguments: MoviePageArguments(movie)); 68 | }, 69 | child: Stack(children: [ 70 | Container( 71 | width: MediaQuery.of(context).size.width, 72 | height: MediaQuery.of(context).size.height, 73 | child: CachedNetworkImage( 74 | imageUrl: movie.heroImages.first, fit: BoxFit.cover)), 75 | Align( 76 | alignment: Alignment.bottomLeft, 77 | child: Container( 78 | height: 120, 79 | decoration: BoxDecoration( 80 | gradient: LinearGradient( 81 | begin: Alignment.topCenter, 82 | end: Alignment.bottomCenter, 83 | colors: [Colors.transparent, Colors.black87])), 84 | )), 85 | Align( 86 | alignment: Alignment.bottomCenter, 87 | child: Padding( 88 | padding: EdgeInsets.fromLTRB(16, 0, 16, 20), 89 | child: Stack( 90 | children: [ 91 | Align( 92 | alignment: Alignment.bottomLeft, 93 | child: Text( 94 | movie.title, 95 | textAlign: TextAlign.left, 96 | style: TextStyle( 97 | fontSize: 22, 98 | color: Colors.white, 99 | fontWeight: FontWeight.bold), 100 | )), 101 | Align( 102 | alignment: Alignment.bottomRight, 103 | child: RichText( 104 | text: TextSpan( 105 | text: '${currentPage + 1}', 106 | style: TextStyle( 107 | color: Color(0xffee5c32), fontSize: 12), 108 | children: [ 109 | TextSpan( 110 | text: ' / $totalPage', 111 | style: TextStyle( 112 | color: Colors.white, fontSize: 12)) 113 | ], 114 | ))), 115 | ], 116 | ))) 117 | ])); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/src/ui/search/search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_tubi/src/blocs/bloc_event.dart'; 4 | import 'package:flutter_tubi/src/blocs/bloc_state.dart'; 5 | import 'package:flutter_tubi/src/blocs/search_bloc.dart'; 6 | import 'package:flutter_tubi/src/models/movie.dart'; 7 | import 'package:flutter_tubi/src/ui/category/movie_category.dart'; 8 | 9 | class SearchPage extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _SearchPageState(); 13 | } 14 | } 15 | 16 | class _SearchPageState extends State { 17 | SearchBloc _bloc = SearchBloc(); 18 | String key; 19 | 20 | /// Should request these via the API. 21 | List defaults = [ 22 | "The Man", 23 | "Moby Dick", 24 | "Palm Swings", 25 | "Condo, The", 26 | "American Beach House", 27 | "Bikini Model Academy", 28 | "Blue My Mind", 29 | "Baby Fever", 30 | "All Out Dynfunktion!", 31 | ]; 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return BlocBuilder( 41 | bloc: _bloc, 42 | builder: (context, state) { 43 | return SafeArea( 44 | child: Scaffold( 45 | backgroundColor: Color(0xff26262d), 46 | body: GestureDetector( 47 | behavior: HitTestBehavior.opaque, 48 | onTap: () { 49 | _hideKeyboard(context); 50 | }, 51 | child: Container( 52 | margin: EdgeInsets.only(top: 20), 53 | child: Column(children: [ 54 | _buildSearchBar(context), 55 | Expanded( 56 | child: (key == null || key.isEmpty) 57 | ? _buildDefaultList() 58 | : _buildSearchResultList()) 59 | ]), 60 | )))); 61 | }); 62 | } 63 | 64 | _buildSearchBar(BuildContext context) { 65 | return Padding( 66 | padding: EdgeInsets.symmetric(horizontal: 40), 67 | child: TextField( 68 | controller: TextEditingController(text: key), 69 | style: TextStyle(fontSize: 16), 70 | textInputAction: TextInputAction.search, 71 | decoration: InputDecoration( 72 | hintText: "Find movies, TV shows & more", 73 | contentPadding: EdgeInsets.symmetric(vertical: 10), 74 | prefixIcon: Icon(Icons.search), 75 | border: OutlineInputBorder( 76 | borderSide: BorderSide(color: Colors.grey), 77 | borderRadius: BorderRadius.all(Radius.circular(25.0)))), 78 | onSubmitted: (text) { 79 | this.key = text; 80 | _searchAction(key: text); 81 | }, 82 | onChanged: (text) { 83 | this.key = text; 84 | if (text.isEmpty) { 85 | setState(() {}); 86 | } 87 | }, 88 | )); 89 | } 90 | 91 | _buildDefaultList() { 92 | return Padding( 93 | padding: EdgeInsets.only(top: 30), 94 | child: Stack( 95 | children: [ 96 | Align( 97 | alignment: Alignment.topCenter, 98 | child: Container( 99 | child: Text( 100 | "TRENDING SEARCHES", 101 | style: TextStyle(fontSize: 16, color: Colors.grey), 102 | )), 103 | ), 104 | Container( 105 | margin: EdgeInsets.only(top: 50), 106 | child: ListView.builder( 107 | itemCount: defaults.length, 108 | itemBuilder: (context, index) { 109 | var value = defaults[index]; 110 | return Padding( 111 | padding: EdgeInsets.only(right: 6.0), 112 | child: GestureDetector( 113 | child: Text( 114 | value, 115 | textAlign: TextAlign.center, 116 | style: TextStyle( 117 | fontWeight: FontWeight.w600, fontSize: 18), 118 | ), 119 | onTap: () { 120 | _searchAction(key: value); 121 | })); 122 | }, 123 | itemExtent: 50)) 124 | ], 125 | )); 126 | } 127 | 128 | Widget _buildSearchResultList() { 129 | final state = _bloc.currentState; 130 | 131 | if (state is BlocStateLoaded) { 132 | final movies = state.data as List; 133 | return MovieCategoryPage( 134 | movies: movies, title: "Search Result", showAppBar: false); 135 | } 136 | 137 | if (state is BlocStateError) { 138 | return Center( 139 | child: Text(state.error.toString()), 140 | ); 141 | } 142 | 143 | return Center( 144 | child: CircularProgressIndicator( 145 | valueColor: AlwaysStoppedAnimation(Colors.grey))); 146 | } 147 | 148 | _searchAction({@required String key}) { 149 | this.key = key; 150 | _bloc.dispatch(BlocEventFetch()); 151 | } 152 | 153 | _hideKeyboard(BuildContext context) { 154 | FocusScope.of(context).requestFocus(FocusNode()); 155 | } 156 | 157 | @override 158 | void dispose() { 159 | _bloc.dispose(); 160 | super.dispose(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/src/ui/video_player/video_player.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_tubi/src/models/models.dart'; 6 | import 'package:video_player/video_player.dart'; 7 | import './video_duration_toolbar.dart'; 8 | import './video_paly_controls.dart'; 9 | import 'share_data_widget.dart'; 10 | 11 | class VideoPlayerPageArguments { 12 | static final routeName = "videoPlayer"; 13 | final Movie movie; 14 | final String url; 15 | 16 | VideoPlayerPageArguments({@required this.movie, @required this.url}); 17 | } 18 | 19 | class VideoPlayerPage extends StatefulWidget { 20 | final Movie movie; 21 | final String url; 22 | 23 | VideoPlayerPage({Key key, @required this.movie, @required this.url}) 24 | : super(key: key); 25 | 26 | @override 27 | State createState() { 28 | return _VideoPlayerPageState(); 29 | } 30 | } 31 | 32 | class _VideoPlayerPageState extends State { 33 | VideoPlayerController _controller; 34 | 35 | ValueNotifier _controllerState; 36 | 37 | VoidCallback _controllerValueListener; 38 | 39 | bool _showControls = false; 40 | 41 | _VideoPlayerPageState() { 42 | _controllerValueListener = () { 43 | if (mounted) { 44 | setState(() {}); 45 | } 46 | }; 47 | } 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | 53 | _statusBarEnable(false); 54 | 55 | _controller = VideoPlayerController.network(widget.url) 56 | ..initialize().then((_) { 57 | // Ensure the first frame is shown after the video is initialized, even before the play button has been pressed. 58 | setState(() { 59 | if (_controller.value.initialized) { 60 | _controller.play(); 61 | } 62 | }); 63 | }); 64 | 65 | SystemChrome.setPreferredOrientations([ 66 | DeviceOrientation.landscapeRight, 67 | DeviceOrientation.landscapeLeft, 68 | ]); 69 | 70 | _controllerState = ValueNotifier(_controller.value); 71 | _controllerState.addListener(() { 72 | this.setState(() {}); 73 | }); 74 | 75 | _controller.addListener(_controllerValueListener); 76 | } 77 | 78 | @override 79 | void deactivate() { 80 | _controller.removeListener(_controllerValueListener); 81 | _statusBarEnable(true); 82 | 83 | super.deactivate(); 84 | } 85 | 86 | @override 87 | Widget build(BuildContext context) { 88 | final ready = _controller.value.initialized; 89 | return MaterialApp( 90 | home: GestureDetector( 91 | onTap: () { 92 | this.setState(() { 93 | _showControls = !_showControls; 94 | }); 95 | }, 96 | child: Stack( 97 | children: [ 98 | ready 99 | ? SizedBox.expand( 100 | child: AspectRatio( 101 | aspectRatio: _controller.value.aspectRatio, 102 | child: VideoPlayer(_controller), 103 | )) 104 | : Container(), 105 | (_showControls ? _buildAppBar(context) : Container()), 106 | Stack( 107 | children: [ 108 | _buildControls(context), 109 | ready ? Container() : _buildLoading() 110 | ], 111 | ) 112 | ], 113 | ))); 114 | } 115 | 116 | AppBar _buildAppBar(BuildContext context) { 117 | return AppBar( 118 | backgroundColor: Colors.transparent, 119 | title: GestureDetector( 120 | child: Row( 121 | children: [ 122 | IconButton( 123 | onPressed: () { 124 | Navigator.pop(context); 125 | }, 126 | icon: Icon( 127 | Icons.arrow_back, 128 | color: Colors.white, 129 | )), 130 | Text(widget.movie.title) 131 | ], 132 | ))); 133 | } 134 | 135 | Widget _buildLoading() { 136 | return Center( 137 | child: SizedBox( 138 | width: 20, 139 | height: 20, 140 | child: CircularProgressIndicator( 141 | valueColor: AlwaysStoppedAnimation(Colors.grey)))); 142 | } 143 | 144 | Widget _buildToolBar({VideoPlayerValue value}) { 145 | return ShareDataWidget( 146 | data: _controller.value, 147 | child: VideoDurationToolBar(controller: _controller)); 148 | } 149 | 150 | Widget _buildControls(BuildContext context) { 151 | if (!_showControls) { 152 | return Container(); 153 | } 154 | return SafeArea( 155 | minimum: EdgeInsets.only(left: 20, right: 20, bottom: 20), 156 | child: Stack(children: [ 157 | Align( 158 | alignment: Alignment.center, 159 | child: Container( 160 | margin: EdgeInsets.only(bottom: 30), 161 | height: 60, 162 | child: ShareDataWidget( 163 | data: _controller.value, 164 | child: VideoPlayControls( 165 | controller: _controller, 166 | togglePlayAction: () { 167 | setState(() { 168 | _controller.value.isPlaying 169 | ? _controller.pause() 170 | : _controller.play(); 171 | }); 172 | }, 173 | forwardAction: () { 174 | setState(() { 175 | _forwardAction(); 176 | }); 177 | }, 178 | backwardAction: () { 179 | setState(() { 180 | _backwardAction(); 181 | }); 182 | }, 183 | )), 184 | )), 185 | Align( 186 | alignment: Alignment.bottomCenter, 187 | child: _buildToolBar(), 188 | ) 189 | ])); 190 | } 191 | 192 | @override 193 | void dispose() { 194 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 195 | 196 | _controller.dispose(); 197 | super.dispose(); 198 | } 199 | 200 | _backwardAction() { 201 | if (_controller.value == null) return; 202 | 203 | var position = _controller.value.position.inSeconds; 204 | position = max(0, position - 15); 205 | 206 | _controller.seekTo(Duration(seconds: position)); 207 | } 208 | 209 | _forwardAction() { 210 | if (_controller.value == null) return; 211 | 212 | var position = _controller.value.position.inSeconds; 213 | position = min(_controller.value.duration.inSeconds, position + 15); 214 | 215 | _controller.seekTo(Duration(seconds: position)); 216 | } 217 | 218 | _statusBarEnable(bool enable) { 219 | SystemChrome.setEnabledSystemUIOverlays( 220 | enable ? SystemUiOverlay.values : []); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/src/ui/movie_page/movie_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:flutter_tubi/src/blocs/bloc_event.dart'; 5 | import 'package:flutter_tubi/src/blocs/bloc_state.dart'; 6 | import 'package:flutter_tubi/src/blocs/movie_details_bloc.dart'; 7 | import 'package:flutter_tubi/src/repositories/http_client.dart'; 8 | import 'package:flutter_tubi/src/repositories/repositories.dart'; 9 | import 'package:flutter_tubi/src/ui/home/home_movie_scroll_row.dart'; 10 | import 'package:flutter_tubi/src/ui/video_player/video_player.dart'; 11 | import '../../models/models.dart'; 12 | import '../../blocs/related_movies/bloc.dart'; 13 | 14 | class MoviePageArguments { 15 | static final routeName = "movies"; 16 | final Movie movie; 17 | 18 | MoviePageArguments(this.movie); 19 | } 20 | 21 | class MoviePage extends StatefulWidget { 22 | final Movie movie; 23 | 24 | const MoviePage({Key key, @required this.movie}) : super(key: key); 25 | 26 | @override 27 | State createState() { 28 | return _MoviePageState(); 29 | } 30 | } 31 | 32 | class _MoviePageState extends State { 33 | Movie get movie => widget.movie; 34 | RelatedBloc _bloc; 35 | MovieDetailsBloc _movieDtailsBloc; 36 | 37 | final apiClient = ApiClient(); 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | 43 | var repo = RelatedRepository(); 44 | _bloc = RelatedBloc(repository: repo, id: movie.id); 45 | _bloc.dispatch(FetchRelatedMoviesEvent()); 46 | 47 | _movieDtailsBloc = MovieDetailsBloc(contentId: movie.id); 48 | _movieDtailsBloc.dispatch(BlocEventFetch()); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | var topImageHeight = MediaQuery.of(context).size.width * 0.7; 54 | 55 | return BlocBuilder( 56 | bloc: _bloc, 57 | builder: (context, state) { 58 | final isPortraitMode = 59 | MediaQuery.of(context).orientation == Orientation.portrait; 60 | 61 | return Scaffold( 62 | backgroundColor: Color(0xff26262d), 63 | appBar: AppBar( 64 | backgroundColor: Color(0xff26262d), 65 | title: Text('${movie.title}'), 66 | ), 67 | body: SafeArea( 68 | child: Stack( 69 | children: [ 70 | Align( 71 | alignment: Alignment.topCenter, 72 | child: _buildImageView( 73 | context: context, height: topImageHeight)), 74 | SizedBox( 75 | width: MediaQuery.of(context).size.width, 76 | height: MediaQuery.of(context).size.height, 77 | child: ListView( 78 | padding: EdgeInsets.only( 79 | top: isPortraitMode ? (topImageHeight - 160) : 30), 80 | children: [ 81 | _buildInfoRow(context), 82 | _buildControlsRow(context), 83 | _buildDescriptionRow(context), 84 | Container( 85 | child: _buildRelatedRow(state: state), 86 | ) 87 | ], 88 | )) 89 | ], 90 | ))); 91 | }); 92 | } 93 | 94 | Widget _buildImageView({BuildContext context, double height}) { 95 | return SizedBox( 96 | child: Stack(children: [ 97 | Container( 98 | height: height, 99 | width: MediaQuery.of(context).size.width, 100 | child: CachedNetworkImage( 101 | imageUrl: movie.backgrounds.first, 102 | placeholder: (context, url) { 103 | return Center( 104 | child: SizedBox( 105 | width: 20, 106 | height: 20, 107 | child: CircularProgressIndicator( 108 | valueColor: 109 | AlwaysStoppedAnimation(Colors.grey)))); 110 | }, 111 | fit: BoxFit.cover)), 112 | SizedBox.expand( 113 | child: Container( 114 | height: 300, 115 | decoration: BoxDecoration( 116 | gradient: LinearGradient( 117 | begin: Alignment.topCenter, 118 | end: Alignment.center, 119 | stops: [0.5, 1.0], 120 | colors: [Colors.black38, Color(0xff26262d)])))), 121 | ])); 122 | } 123 | 124 | Widget _buildInfoRow(BuildContext context) { 125 | return Container( 126 | padding: EdgeInsets.fromLTRB(12, 12, 12, 12), 127 | child: Stack( 128 | alignment: Alignment.centerLeft, 129 | children: [ 130 | Row( 131 | mainAxisSize: MainAxisSize.min, 132 | mainAxisAlignment: MainAxisAlignment.start, 133 | children: [ 134 | SizedBox( 135 | width: 124, 136 | height: 180, 137 | child: CachedNetworkImage( 138 | imageUrl: movie.posterarts.first, fit: BoxFit.cover)), 139 | SizedBox(width: 10), 140 | SizedBox( 141 | width: 200, 142 | child: Column( 143 | crossAxisAlignment: CrossAxisAlignment.start, 144 | mainAxisAlignment: MainAxisAlignment.center, 145 | children: [ 146 | Text('${movie.title}', 147 | style: TextStyle( 148 | fontWeight: FontWeight.bold, fontSize: 26)), 149 | SizedBox(height: 30), 150 | (movie.year > 0 151 | ? Text( 152 | '(${movie.year}) · ${this._formatDuration(movie.duration)}\n${movie.tags.join()} ', 153 | style: 154 | TextStyle(color: Colors.grey, fontSize: 12)) 155 | : Text('')) 156 | ], 157 | )), 158 | ], 159 | ), 160 | Align( 161 | alignment: Alignment.bottomRight, 162 | child: _ratingText(movie.ratings)) 163 | ], 164 | )); 165 | } 166 | 167 | Widget _buildControlsRow(BuildContext context) { 168 | return Container( 169 | width: MediaQuery.of(context).size.width, 170 | child: Row( 171 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 172 | children: [ 173 | Column(children: [ 174 | SizedBox( 175 | width: 60, 176 | height: 60, 177 | child: IconButton( 178 | onPressed: () {}, 179 | icon: Image.asset("assets/icons/add_to_queue_normal.png"), 180 | )), 181 | Text('My Queue', style: TextStyle(fontSize: 12)), 182 | ]), 183 | SizedBox( 184 | width: 100, 185 | height: 100, 186 | child: IconButton( 187 | onPressed: () { 188 | final currentState = this._movieDtailsBloc.currentState; 189 | if (currentState is BlocStateLoading) { 190 | _showAlertDialog(context, 191 | title: 'Loading', 192 | message: 193 | 'Movie content is loading. Please try again later.'); 194 | } 195 | 196 | if (currentState is BlocStateError) { 197 | _showAlertDialog(context, 198 | title: 'Error', 199 | message: currentState.error.toString()); 200 | } 201 | 202 | if (currentState is BlocStateLoaded) { 203 | var theMovie = currentState.data as Movie; 204 | if (theMovie.videoResources.isNotEmpty && 205 | theMovie.videoResources[0].manifest.url != null) { 206 | Navigator.of(context, rootNavigator: true).pushNamed( 207 | VideoPlayerPageArguments.routeName, 208 | arguments: VideoPlayerPageArguments( 209 | movie: theMovie, 210 | url: 211 | theMovie.videoResources[0].manifest.url)); 212 | } else { 213 | _showAlertDialog(context, 214 | title: 'Error', 215 | message: 'Error loading movie resource.'); 216 | } 217 | } 218 | }, 219 | icon: Image.asset("assets/icons/play_large_normal.png"), 220 | )), 221 | Column( 222 | children: [ 223 | SizedBox( 224 | width: 60, 225 | height: 60, 226 | child: IconButton( 227 | onPressed: () {}, 228 | icon: Image.asset("assets/icons/share_normal.png"), 229 | )), 230 | Text('Share', style: TextStyle(fontSize: 12)), 231 | ], 232 | ) 233 | ])); 234 | } 235 | 236 | Widget _buildDescriptionRow(BuildContext context) { 237 | var textSpans = List(); 238 | if (movie.directors != null && movie.directors.isNotEmpty) { 239 | textSpans.addAll([ 240 | TextSpan(text: 'Director ', style: TextStyle(color: Colors.grey)), 241 | TextSpan( 242 | text: movie.directors.join(', '), 243 | style: TextStyle(color: Colors.white)) 244 | ]); 245 | } 246 | 247 | if (movie.actors != null && movie.actors.isNotEmpty) { 248 | textSpans.addAll([ 249 | TextSpan(text: '\n'), 250 | TextSpan(text: '\n'), 251 | TextSpan(text: 'Starring ', style: TextStyle(color: Colors.grey)), 252 | TextSpan( 253 | text: movie.actors.join(', '), 254 | style: TextStyle(color: Colors.white, height: 1.2)) 255 | ]); 256 | } 257 | 258 | var richText = RichText( 259 | textAlign: TextAlign.left, 260 | text: TextSpan( 261 | style: DefaultTextStyle.of(context).style.apply(), 262 | children: textSpans, 263 | )); 264 | 265 | return Container( 266 | margin: EdgeInsets.all(16), 267 | child: Column( 268 | crossAxisAlignment: CrossAxisAlignment.start, 269 | children: [ 270 | Text( 271 | movie.description, 272 | style: TextStyle(fontSize: 16), 273 | ), 274 | SizedBox( 275 | height: 40, 276 | ), 277 | richText 278 | ], 279 | ), 280 | ); 281 | } 282 | 283 | Widget _ratingText(List> ratings) { 284 | var value = ratings.first['value'] ?? ''; 285 | return ClipRRect( 286 | borderRadius: BorderRadius.circular(2.0), 287 | child: Container( 288 | color: Color(0xff474747), 289 | child: Padding( 290 | padding: EdgeInsets.all(6), 291 | child: Text( 292 | value, 293 | style: TextStyle(fontWeight: FontWeight.w600, fontSize: 12), 294 | )))); 295 | } 296 | 297 | Widget _buildRelatedRow({@required RelatedState state}) { 298 | if (state is RelatedLoaded) { 299 | if (state.movies.isEmpty) { 300 | return Container(); 301 | } 302 | return Column(children: [ 303 | Align( 304 | alignment: Alignment.topLeft, 305 | child: Padding( 306 | padding: EdgeInsets.only(left: 16, top: 16), 307 | child: Text('You May Also Like', 308 | style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500)), 309 | ), 310 | ), 311 | Container( 312 | margin: EdgeInsets.only(top: 20), 313 | height: 270, 314 | child: HomeMovieScrollRow(movies: state.movies, key: UniqueKey())) 315 | ]); 316 | } 317 | 318 | if (state is RelatedError) { 319 | return Container(); 320 | } 321 | return Padding( 322 | padding: EdgeInsets.symmetric(vertical: 30.0), 323 | child: Center( 324 | child: Container( 325 | width: 20, 326 | height: 20, 327 | child: CircularProgressIndicator( 328 | valueColor: AlwaysStoppedAnimation(Colors.grey))))); 329 | } 330 | 331 | _showAlertDialog(BuildContext context, {String title, String message}) { 332 | showDialog( 333 | context: context, 334 | builder: (BuildContext context) { 335 | return AlertDialog( 336 | title: Text(title), 337 | content: SingleChildScrollView( 338 | child: ListBody( 339 | children: [ 340 | Text(message), 341 | ], 342 | ), 343 | ), 344 | actions: [ 345 | FlatButton( 346 | child: Text('Cancel'), 347 | onPressed: () { 348 | Navigator.of(context).pop(); 349 | }, 350 | ) 351 | ], 352 | ); 353 | }); 354 | } 355 | 356 | String _formatDuration(int seconds) { 357 | if (seconds == null) return ''; 358 | if (seconds <= 60) { 359 | return '$seconds sec'; 360 | } 361 | if (seconds <= 60 * 60) { 362 | return '${(seconds / 60).floor()} min'; 363 | } 364 | return '${(seconds / 60 / 60).round()} h ${(seconds / 60 % 60).floor()} min'; 365 | } 366 | 367 | @override 368 | void dispose() { 369 | super.dispose(); 370 | _bloc.dispose(); 371 | _movieDtailsBloc.dispose(); 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "0.36.4" 11 | archive: 12 | dependency: transitive 13 | description: 14 | name: archive 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.0.11" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.5.2" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "2.4.0" 32 | bloc: 33 | dependency: "direct main" 34 | description: 35 | name: bloc 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "0.10.0" 39 | boolean_selector: 40 | dependency: transitive 41 | description: 42 | name: boolean_selector 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.0.5" 46 | build: 47 | dependency: transitive 48 | description: 49 | name: build 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.2.2" 53 | build_config: 54 | dependency: transitive 55 | description: 56 | name: build_config 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "0.4.2" 60 | build_daemon: 61 | dependency: transitive 62 | description: 63 | name: build_daemon 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "2.1.3" 67 | build_resolvers: 68 | dependency: transitive 69 | description: 70 | name: build_resolvers 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "1.2.1" 74 | build_runner: 75 | dependency: "direct dev" 76 | description: 77 | name: build_runner 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "1.7.4" 81 | build_runner_core: 82 | dependency: transitive 83 | description: 84 | name: build_runner_core 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "4.4.0" 88 | built_collection: 89 | dependency: transitive 90 | description: 91 | name: built_collection 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "4.3.2" 95 | built_value: 96 | dependency: transitive 97 | description: 98 | name: built_value 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "7.0.9" 102 | cached_network_image: 103 | dependency: "direct main" 104 | description: 105 | name: cached_network_image 106 | url: "https://pub.flutter-io.cn" 107 | source: hosted 108 | version: "2.0.0" 109 | charcode: 110 | dependency: transitive 111 | description: 112 | name: charcode 113 | url: "https://pub.flutter-io.cn" 114 | source: hosted 115 | version: "1.1.2" 116 | checked_yaml: 117 | dependency: transitive 118 | description: 119 | name: checked_yaml 120 | url: "https://pub.flutter-io.cn" 121 | source: hosted 122 | version: "1.0.2" 123 | code_builder: 124 | dependency: transitive 125 | description: 126 | name: code_builder 127 | url: "https://pub.flutter-io.cn" 128 | source: hosted 129 | version: "3.2.1" 130 | collection: 131 | dependency: transitive 132 | description: 133 | name: collection 134 | url: "https://pub.flutter-io.cn" 135 | source: hosted 136 | version: "1.14.11" 137 | convert: 138 | dependency: transitive 139 | description: 140 | name: convert 141 | url: "https://pub.flutter-io.cn" 142 | source: hosted 143 | version: "2.1.1" 144 | crypto: 145 | dependency: transitive 146 | description: 147 | name: crypto 148 | url: "https://pub.flutter-io.cn" 149 | source: hosted 150 | version: "2.1.3" 151 | csslib: 152 | dependency: transitive 153 | description: 154 | name: csslib 155 | url: "https://pub.flutter-io.cn" 156 | source: hosted 157 | version: "0.16.1" 158 | cupertino_icons: 159 | dependency: "direct main" 160 | description: 161 | name: cupertino_icons 162 | url: "https://pub.flutter-io.cn" 163 | source: hosted 164 | version: "0.1.3" 165 | dart_style: 166 | dependency: transitive 167 | description: 168 | name: dart_style 169 | url: "https://pub.flutter-io.cn" 170 | source: hosted 171 | version: "1.2.9" 172 | equatable: 173 | dependency: "direct main" 174 | description: 175 | name: equatable 176 | url: "https://pub.flutter-io.cn" 177 | source: hosted 178 | version: "0.2.6" 179 | fixnum: 180 | dependency: transitive 181 | description: 182 | name: fixnum 183 | url: "https://pub.flutter-io.cn" 184 | source: hosted 185 | version: "0.10.11" 186 | flutter: 187 | dependency: "direct main" 188 | description: flutter 189 | source: sdk 190 | version: "0.0.0" 191 | flutter_bloc: 192 | dependency: "direct main" 193 | description: 194 | name: flutter_bloc 195 | url: "https://pub.flutter-io.cn" 196 | source: hosted 197 | version: "0.8.0" 198 | flutter_cache_manager: 199 | dependency: transitive 200 | description: 201 | name: flutter_cache_manager 202 | url: "https://pub.flutter-io.cn" 203 | source: hosted 204 | version: "1.1.3" 205 | flutter_test: 206 | dependency: "direct dev" 207 | description: flutter 208 | source: sdk 209 | version: "0.0.0" 210 | flutter_web_plugins: 211 | dependency: transitive 212 | description: flutter 213 | source: sdk 214 | version: "0.0.0" 215 | front_end: 216 | dependency: transitive 217 | description: 218 | name: front_end 219 | url: "https://pub.flutter-io.cn" 220 | source: hosted 221 | version: "0.1.19" 222 | glob: 223 | dependency: transitive 224 | description: 225 | name: glob 226 | url: "https://pub.flutter-io.cn" 227 | source: hosted 228 | version: "1.2.0" 229 | graphs: 230 | dependency: transitive 231 | description: 232 | name: graphs 233 | url: "https://pub.flutter-io.cn" 234 | source: hosted 235 | version: "0.2.0" 236 | html: 237 | dependency: transitive 238 | description: 239 | name: html 240 | url: "https://pub.flutter-io.cn" 241 | source: hosted 242 | version: "0.14.0+3" 243 | http: 244 | dependency: "direct main" 245 | description: 246 | name: http 247 | url: "https://pub.flutter-io.cn" 248 | source: hosted 249 | version: "0.12.0+4" 250 | http_multi_server: 251 | dependency: transitive 252 | description: 253 | name: http_multi_server 254 | url: "https://pub.flutter-io.cn" 255 | source: hosted 256 | version: "2.2.0" 257 | http_parser: 258 | dependency: transitive 259 | description: 260 | name: http_parser 261 | url: "https://pub.flutter-io.cn" 262 | source: hosted 263 | version: "3.1.3" 264 | image: 265 | dependency: transitive 266 | description: 267 | name: image 268 | url: "https://pub.flutter-io.cn" 269 | source: hosted 270 | version: "2.1.4" 271 | io: 272 | dependency: transitive 273 | description: 274 | name: io 275 | url: "https://pub.flutter-io.cn" 276 | source: hosted 277 | version: "0.3.3" 278 | js: 279 | dependency: transitive 280 | description: 281 | name: js 282 | url: "https://pub.flutter-io.cn" 283 | source: hosted 284 | version: "0.6.1+1" 285 | json_annotation: 286 | dependency: transitive 287 | description: 288 | name: json_annotation 289 | url: "https://pub.flutter-io.cn" 290 | source: hosted 291 | version: "2.3.0" 292 | json_serializable: 293 | dependency: "direct main" 294 | description: 295 | name: json_serializable 296 | url: "https://pub.flutter-io.cn" 297 | source: hosted 298 | version: "2.3.0" 299 | kernel: 300 | dependency: transitive 301 | description: 302 | name: kernel 303 | url: "https://pub.flutter-io.cn" 304 | source: hosted 305 | version: "0.3.19" 306 | logging: 307 | dependency: transitive 308 | description: 309 | name: logging 310 | url: "https://pub.flutter-io.cn" 311 | source: hosted 312 | version: "0.11.4" 313 | matcher: 314 | dependency: transitive 315 | description: 316 | name: matcher 317 | url: "https://pub.flutter-io.cn" 318 | source: hosted 319 | version: "0.12.6" 320 | meta: 321 | dependency: transitive 322 | description: 323 | name: meta 324 | url: "https://pub.flutter-io.cn" 325 | source: hosted 326 | version: "1.1.8" 327 | mime: 328 | dependency: transitive 329 | description: 330 | name: mime 331 | url: "https://pub.flutter-io.cn" 332 | source: hosted 333 | version: "0.9.6+3" 334 | node_interop: 335 | dependency: transitive 336 | description: 337 | name: node_interop 338 | url: "https://pub.flutter-io.cn" 339 | source: hosted 340 | version: "1.0.3" 341 | node_io: 342 | dependency: transitive 343 | description: 344 | name: node_io 345 | url: "https://pub.flutter-io.cn" 346 | source: hosted 347 | version: "1.0.1+2" 348 | package_config: 349 | dependency: transitive 350 | description: 351 | name: package_config 352 | url: "https://pub.flutter-io.cn" 353 | source: hosted 354 | version: "1.9.1" 355 | package_resolver: 356 | dependency: transitive 357 | description: 358 | name: package_resolver 359 | url: "https://pub.flutter-io.cn" 360 | source: hosted 361 | version: "1.0.10" 362 | path: 363 | dependency: transitive 364 | description: 365 | name: path 366 | url: "https://pub.flutter-io.cn" 367 | source: hosted 368 | version: "1.6.4" 369 | path_provider: 370 | dependency: transitive 371 | description: 372 | name: path_provider 373 | url: "https://pub.flutter-io.cn" 374 | source: hosted 375 | version: "0.5.0+1" 376 | pedantic: 377 | dependency: transitive 378 | description: 379 | name: pedantic 380 | url: "https://pub.flutter-io.cn" 381 | source: hosted 382 | version: "1.8.0+1" 383 | petitparser: 384 | dependency: transitive 385 | description: 386 | name: petitparser 387 | url: "https://pub.flutter-io.cn" 388 | source: hosted 389 | version: "2.4.0" 390 | pool: 391 | dependency: transitive 392 | description: 393 | name: pool 394 | url: "https://pub.flutter-io.cn" 395 | source: hosted 396 | version: "1.4.0" 397 | pub_semver: 398 | dependency: transitive 399 | description: 400 | name: pub_semver 401 | url: "https://pub.flutter-io.cn" 402 | source: hosted 403 | version: "1.4.3" 404 | pubspec_parse: 405 | dependency: transitive 406 | description: 407 | name: pubspec_parse 408 | url: "https://pub.flutter-io.cn" 409 | source: hosted 410 | version: "0.1.5" 411 | quiver: 412 | dependency: transitive 413 | description: 414 | name: quiver 415 | url: "https://pub.flutter-io.cn" 416 | source: hosted 417 | version: "2.0.5" 418 | rxdart: 419 | dependency: transitive 420 | description: 421 | name: rxdart 422 | url: "https://pub.flutter-io.cn" 423 | source: hosted 424 | version: "0.21.0" 425 | shelf: 426 | dependency: transitive 427 | description: 428 | name: shelf 429 | url: "https://pub.flutter-io.cn" 430 | source: hosted 431 | version: "0.7.5" 432 | shelf_web_socket: 433 | dependency: transitive 434 | description: 435 | name: shelf_web_socket 436 | url: "https://pub.flutter-io.cn" 437 | source: hosted 438 | version: "0.2.3" 439 | sky_engine: 440 | dependency: transitive 441 | description: flutter 442 | source: sdk 443 | version: "0.0.99" 444 | source_gen: 445 | dependency: transitive 446 | description: 447 | name: source_gen 448 | url: "https://pub.flutter-io.cn" 449 | source: hosted 450 | version: "0.9.4+4" 451 | source_span: 452 | dependency: transitive 453 | description: 454 | name: source_span 455 | url: "https://pub.flutter-io.cn" 456 | source: hosted 457 | version: "1.5.5" 458 | sqflite: 459 | dependency: transitive 460 | description: 461 | name: sqflite 462 | url: "https://pub.flutter-io.cn" 463 | source: hosted 464 | version: "1.2.1" 465 | stack_trace: 466 | dependency: transitive 467 | description: 468 | name: stack_trace 469 | url: "https://pub.flutter-io.cn" 470 | source: hosted 471 | version: "1.9.3" 472 | stream_channel: 473 | dependency: transitive 474 | description: 475 | name: stream_channel 476 | url: "https://pub.flutter-io.cn" 477 | source: hosted 478 | version: "2.0.0" 479 | stream_transform: 480 | dependency: transitive 481 | description: 482 | name: stream_transform 483 | url: "https://pub.flutter-io.cn" 484 | source: hosted 485 | version: "1.2.0" 486 | string_scanner: 487 | dependency: transitive 488 | description: 489 | name: string_scanner 490 | url: "https://pub.flutter-io.cn" 491 | source: hosted 492 | version: "1.0.5" 493 | synchronized: 494 | dependency: transitive 495 | description: 496 | name: synchronized 497 | url: "https://pub.flutter-io.cn" 498 | source: hosted 499 | version: "2.2.0" 500 | term_glyph: 501 | dependency: transitive 502 | description: 503 | name: term_glyph 504 | url: "https://pub.flutter-io.cn" 505 | source: hosted 506 | version: "1.1.0" 507 | test_api: 508 | dependency: transitive 509 | description: 510 | name: test_api 511 | url: "https://pub.flutter-io.cn" 512 | source: hosted 513 | version: "0.2.14" 514 | timing: 515 | dependency: transitive 516 | description: 517 | name: timing 518 | url: "https://pub.flutter-io.cn" 519 | source: hosted 520 | version: "0.1.1+2" 521 | typed_data: 522 | dependency: transitive 523 | description: 524 | name: typed_data 525 | url: "https://pub.flutter-io.cn" 526 | source: hosted 527 | version: "1.1.6" 528 | uuid: 529 | dependency: transitive 530 | description: 531 | name: uuid 532 | url: "https://pub.flutter-io.cn" 533 | source: hosted 534 | version: "2.0.4" 535 | vector_math: 536 | dependency: transitive 537 | description: 538 | name: vector_math 539 | url: "https://pub.flutter-io.cn" 540 | source: hosted 541 | version: "2.0.8" 542 | video_player: 543 | dependency: "direct main" 544 | description: 545 | name: video_player 546 | url: "https://pub.flutter-io.cn" 547 | source: hosted 548 | version: "0.10.8+1" 549 | video_player_platform_interface: 550 | dependency: transitive 551 | description: 552 | name: video_player_platform_interface 553 | url: "https://pub.flutter-io.cn" 554 | source: hosted 555 | version: "1.0.5" 556 | video_player_web: 557 | dependency: transitive 558 | description: 559 | name: video_player_web 560 | url: "https://pub.flutter-io.cn" 561 | source: hosted 562 | version: "0.1.2+1" 563 | watcher: 564 | dependency: transitive 565 | description: 566 | name: watcher 567 | url: "https://pub.flutter-io.cn" 568 | source: hosted 569 | version: "0.9.7+13" 570 | web_socket_channel: 571 | dependency: transitive 572 | description: 573 | name: web_socket_channel 574 | url: "https://pub.flutter-io.cn" 575 | source: hosted 576 | version: "1.1.0" 577 | xml: 578 | dependency: transitive 579 | description: 580 | name: xml 581 | url: "https://pub.flutter-io.cn" 582 | source: hosted 583 | version: "3.5.0" 584 | yaml: 585 | dependency: transitive 586 | description: 587 | name: yaml 588 | url: "https://pub.flutter-io.cn" 589 | source: hosted 590 | version: "2.2.0" 591 | sdks: 592 | dart: ">=2.7.0 <3.0.0" 593 | flutter: ">=1.12.13+hotfix.4 <2.0.0" 594 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2242DE13C8B893E381F03F74 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 48B0BA9073EF36A0FE3AFD9C /* libPods-Runner.a */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 18 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 19 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 2147483647; 29 | dstPath = ""; 30 | dstSubfolderSpec = 10; 31 | files = ( 32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 34 | ); 35 | name = "Embed Frameworks"; 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 43 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 44 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 45 | 456AE63709C596A9DDEA2D36 /* 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 = ""; }; 46 | 48B0BA9073EF36A0FE3AFD9C /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 48 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 49 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 50 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 51 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 52 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 55 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 56 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 58 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | D3F429F18B886204CA97D53B /* 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 = ""; }; 60 | E68D817A44DFAC90C0F2E43C /* 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 = ""; }; 61 | /* End PBXFileReference section */ 62 | 63 | /* Begin PBXFrameworksBuildPhase section */ 64 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 69 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 70 | 2242DE13C8B893E381F03F74 /* libPods-Runner.a in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 8259EADEE1594F723FED0D20 /* Pods */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | E68D817A44DFAC90C0F2E43C /* Pods-Runner.debug.xcconfig */, 81 | D3F429F18B886204CA97D53B /* Pods-Runner.release.xcconfig */, 82 | 456AE63709C596A9DDEA2D36 /* Pods-Runner.profile.xcconfig */, 83 | ); 84 | path = Pods; 85 | sourceTree = ""; 86 | }; 87 | 9740EEB11CF90186004384FC /* Flutter */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 3B80C3931E831B6300D905FE /* App.framework */, 91 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 92 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 93 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 94 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 95 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 96 | ); 97 | name = Flutter; 98 | sourceTree = ""; 99 | }; 100 | 97C146E51CF9000F007C117D = { 101 | isa = PBXGroup; 102 | children = ( 103 | 9740EEB11CF90186004384FC /* Flutter */, 104 | 97C146F01CF9000F007C117D /* Runner */, 105 | 97C146EF1CF9000F007C117D /* Products */, 106 | 8259EADEE1594F723FED0D20 /* Pods */, 107 | FC5BEB6902275AED51397FEB /* Frameworks */, 108 | ); 109 | sourceTree = ""; 110 | }; 111 | 97C146EF1CF9000F007C117D /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 97C146EE1CF9000F007C117D /* Runner.app */, 115 | ); 116 | name = Products; 117 | sourceTree = ""; 118 | }; 119 | 97C146F01CF9000F007C117D /* Runner */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 123 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 124 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 125 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 126 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 127 | 97C147021CF9000F007C117D /* Info.plist */, 128 | 97C146F11CF9000F007C117D /* Supporting Files */, 129 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 130 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 131 | ); 132 | path = Runner; 133 | sourceTree = ""; 134 | }; 135 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 97C146F21CF9000F007C117D /* main.m */, 139 | ); 140 | name = "Supporting Files"; 141 | sourceTree = ""; 142 | }; 143 | FC5BEB6902275AED51397FEB /* Frameworks */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 48B0BA9073EF36A0FE3AFD9C /* libPods-Runner.a */, 147 | ); 148 | name = Frameworks; 149 | sourceTree = ""; 150 | }; 151 | /* End PBXGroup section */ 152 | 153 | /* Begin PBXNativeTarget section */ 154 | 97C146ED1CF9000F007C117D /* Runner */ = { 155 | isa = PBXNativeTarget; 156 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 157 | buildPhases = ( 158 | 1FFCE4CFE530FCAFF210BEEA /* [CP] Check Pods Manifest.lock */, 159 | 9740EEB61CF901F6004384FC /* Run Script */, 160 | 97C146EA1CF9000F007C117D /* Sources */, 161 | 97C146EB1CF9000F007C117D /* Frameworks */, 162 | 97C146EC1CF9000F007C117D /* Resources */, 163 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 164 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 165 | 3D65200729A76E82719522F2 /* [CP] Embed Pods Frameworks */, 166 | ); 167 | buildRules = ( 168 | ); 169 | dependencies = ( 170 | ); 171 | name = Runner; 172 | productName = Runner; 173 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 174 | productType = "com.apple.product-type.application"; 175 | }; 176 | /* End PBXNativeTarget section */ 177 | 178 | /* Begin PBXProject section */ 179 | 97C146E61CF9000F007C117D /* Project object */ = { 180 | isa = PBXProject; 181 | attributes = { 182 | LastUpgradeCheck = 0910; 183 | ORGANIZATIONNAME = "The Chromium Authors"; 184 | TargetAttributes = { 185 | 97C146ED1CF9000F007C117D = { 186 | CreatedOnToolsVersion = 7.3.1; 187 | DevelopmentTeam = Z9KD6Z92BF; 188 | ProvisioningStyle = Automatic; 189 | }; 190 | }; 191 | }; 192 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 193 | compatibilityVersion = "Xcode 3.2"; 194 | developmentRegion = English; 195 | hasScannedForEncodings = 0; 196 | knownRegions = ( 197 | English, 198 | en, 199 | Base, 200 | ); 201 | mainGroup = 97C146E51CF9000F007C117D; 202 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 203 | projectDirPath = ""; 204 | projectRoot = ""; 205 | targets = ( 206 | 97C146ED1CF9000F007C117D /* Runner */, 207 | ); 208 | }; 209 | /* End PBXProject section */ 210 | 211 | /* Begin PBXResourcesBuildPhase section */ 212 | 97C146EC1CF9000F007C117D /* Resources */ = { 213 | isa = PBXResourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 217 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 218 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 219 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 220 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXResourcesBuildPhase section */ 225 | 226 | /* Begin PBXShellScriptBuildPhase section */ 227 | 1FFCE4CFE530FCAFF210BEEA /* [CP] Check Pods Manifest.lock */ = { 228 | isa = PBXShellScriptBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | ); 232 | inputFileListPaths = ( 233 | ); 234 | inputPaths = ( 235 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 236 | "${PODS_ROOT}/Manifest.lock", 237 | ); 238 | name = "[CP] Check Pods Manifest.lock"; 239 | outputFileListPaths = ( 240 | ); 241 | outputPaths = ( 242 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | shellPath = /bin/sh; 246 | 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"; 247 | showEnvVarsInLog = 0; 248 | }; 249 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 250 | isa = PBXShellScriptBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | ); 254 | inputPaths = ( 255 | ); 256 | name = "Thin Binary"; 257 | outputPaths = ( 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | shellPath = /bin/sh; 261 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 262 | }; 263 | 3D65200729A76E82719522F2 /* [CP] Embed Pods Frameworks */ = { 264 | isa = PBXShellScriptBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | ); 268 | inputPaths = ( 269 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 270 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 271 | ); 272 | name = "[CP] Embed Pods Frameworks"; 273 | outputPaths = ( 274 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | shellPath = /bin/sh; 278 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 279 | showEnvVarsInLog = 0; 280 | }; 281 | 9740EEB61CF901F6004384FC /* Run Script */ = { 282 | isa = PBXShellScriptBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | inputPaths = ( 287 | ); 288 | name = "Run Script"; 289 | outputPaths = ( 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | shellPath = /bin/sh; 293 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 294 | }; 295 | /* End PBXShellScriptBuildPhase section */ 296 | 297 | /* Begin PBXSourcesBuildPhase section */ 298 | 97C146EA1CF9000F007C117D /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 303 | 97C146F31CF9000F007C117D /* main.m in Sources */, 304 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXSourcesBuildPhase section */ 309 | 310 | /* Begin PBXVariantGroup section */ 311 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 312 | isa = PBXVariantGroup; 313 | children = ( 314 | 97C146FB1CF9000F007C117D /* Base */, 315 | ); 316 | name = Main.storyboard; 317 | sourceTree = ""; 318 | }; 319 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 97C147001CF9000F007C117D /* Base */, 323 | ); 324 | name = LaunchScreen.storyboard; 325 | sourceTree = ""; 326 | }; 327 | /* End PBXVariantGroup section */ 328 | 329 | /* Begin XCBuildConfiguration section */ 330 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 331 | isa = XCBuildConfiguration; 332 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 333 | buildSettings = { 334 | ALWAYS_SEARCH_USER_PATHS = NO; 335 | CLANG_ANALYZER_NONNULL = YES; 336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 337 | CLANG_CXX_LIBRARY = "libc++"; 338 | CLANG_ENABLE_MODULES = YES; 339 | CLANG_ENABLE_OBJC_ARC = YES; 340 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 341 | CLANG_WARN_BOOL_CONVERSION = YES; 342 | CLANG_WARN_COMMA = YES; 343 | CLANG_WARN_CONSTANT_CONVERSION = YES; 344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INFINITE_RECURSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 351 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 352 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 353 | CLANG_WARN_STRICT_PROTOTYPES = YES; 354 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 355 | CLANG_WARN_UNREACHABLE_CODE = YES; 356 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 357 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 358 | COPY_PHASE_STRIP = NO; 359 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 360 | ENABLE_NS_ASSERTIONS = NO; 361 | ENABLE_STRICT_OBJC_MSGSEND = YES; 362 | GCC_C_LANGUAGE_STANDARD = gnu99; 363 | GCC_NO_COMMON_BLOCKS = YES; 364 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 365 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 366 | GCC_WARN_UNDECLARED_SELECTOR = YES; 367 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 368 | GCC_WARN_UNUSED_FUNCTION = YES; 369 | GCC_WARN_UNUSED_VARIABLE = YES; 370 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 371 | MTL_ENABLE_DEBUG_INFO = NO; 372 | SDKROOT = iphoneos; 373 | TARGETED_DEVICE_FAMILY = "1,2"; 374 | VALIDATE_PRODUCT = YES; 375 | }; 376 | name = Profile; 377 | }; 378 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 379 | isa = XCBuildConfiguration; 380 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 381 | buildSettings = { 382 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 383 | CODE_SIGN_IDENTITY = "iPhone Developer"; 384 | CODE_SIGN_STYLE = Automatic; 385 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 386 | DEVELOPMENT_TEAM = Z9KD6Z92BF; 387 | ENABLE_BITCODE = NO; 388 | FRAMEWORK_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "$(PROJECT_DIR)/Flutter", 391 | ); 392 | INFOPLIST_FILE = Runner/Info.plist; 393 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 394 | LIBRARY_SEARCH_PATHS = ( 395 | "$(inherited)", 396 | "$(PROJECT_DIR)/Flutter", 397 | ); 398 | PRODUCT_BUNDLE_IDENTIFIER = com.weizhi.ios; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | PROVISIONING_PROFILE_SPECIFIER = ""; 401 | VERSIONING_SYSTEM = "apple-generic"; 402 | }; 403 | name = Profile; 404 | }; 405 | 97C147031CF9000F007C117D /* Debug */ = { 406 | isa = XCBuildConfiguration; 407 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 408 | buildSettings = { 409 | ALWAYS_SEARCH_USER_PATHS = NO; 410 | CLANG_ANALYZER_NONNULL = YES; 411 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 412 | CLANG_CXX_LIBRARY = "libc++"; 413 | CLANG_ENABLE_MODULES = YES; 414 | CLANG_ENABLE_OBJC_ARC = YES; 415 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 416 | CLANG_WARN_BOOL_CONVERSION = YES; 417 | CLANG_WARN_COMMA = YES; 418 | CLANG_WARN_CONSTANT_CONVERSION = YES; 419 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 420 | CLANG_WARN_EMPTY_BODY = YES; 421 | CLANG_WARN_ENUM_CONVERSION = YES; 422 | CLANG_WARN_INFINITE_RECURSION = YES; 423 | CLANG_WARN_INT_CONVERSION = YES; 424 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = dwarf; 435 | ENABLE_STRICT_OBJC_MSGSEND = YES; 436 | ENABLE_TESTABILITY = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu99; 438 | GCC_DYNAMIC_NO_PIC = NO; 439 | GCC_NO_COMMON_BLOCKS = YES; 440 | GCC_OPTIMIZATION_LEVEL = 0; 441 | GCC_PREPROCESSOR_DEFINITIONS = ( 442 | "DEBUG=1", 443 | "$(inherited)", 444 | ); 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 452 | MTL_ENABLE_DEBUG_INFO = YES; 453 | ONLY_ACTIVE_ARCH = YES; 454 | SDKROOT = iphoneos; 455 | TARGETED_DEVICE_FAMILY = "1,2"; 456 | }; 457 | name = Debug; 458 | }; 459 | 97C147041CF9000F007C117D /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 462 | buildSettings = { 463 | ALWAYS_SEARCH_USER_PATHS = NO; 464 | CLANG_ANALYZER_NONNULL = YES; 465 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 466 | CLANG_CXX_LIBRARY = "libc++"; 467 | CLANG_ENABLE_MODULES = YES; 468 | CLANG_ENABLE_OBJC_ARC = YES; 469 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 470 | CLANG_WARN_BOOL_CONVERSION = YES; 471 | CLANG_WARN_COMMA = YES; 472 | CLANG_WARN_CONSTANT_CONVERSION = YES; 473 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 474 | CLANG_WARN_EMPTY_BODY = YES; 475 | CLANG_WARN_ENUM_CONVERSION = YES; 476 | CLANG_WARN_INFINITE_RECURSION = YES; 477 | CLANG_WARN_INT_CONVERSION = YES; 478 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 479 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 480 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 481 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 482 | CLANG_WARN_STRICT_PROTOTYPES = YES; 483 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 484 | CLANG_WARN_UNREACHABLE_CODE = YES; 485 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 486 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 487 | COPY_PHASE_STRIP = NO; 488 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 489 | ENABLE_NS_ASSERTIONS = NO; 490 | ENABLE_STRICT_OBJC_MSGSEND = YES; 491 | GCC_C_LANGUAGE_STANDARD = gnu99; 492 | GCC_NO_COMMON_BLOCKS = YES; 493 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 494 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 495 | GCC_WARN_UNDECLARED_SELECTOR = YES; 496 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 497 | GCC_WARN_UNUSED_FUNCTION = YES; 498 | GCC_WARN_UNUSED_VARIABLE = YES; 499 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 500 | MTL_ENABLE_DEBUG_INFO = NO; 501 | SDKROOT = iphoneos; 502 | TARGETED_DEVICE_FAMILY = "1,2"; 503 | VALIDATE_PRODUCT = YES; 504 | }; 505 | name = Release; 506 | }; 507 | 97C147061CF9000F007C117D /* Debug */ = { 508 | isa = XCBuildConfiguration; 509 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 510 | buildSettings = { 511 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 512 | CODE_SIGN_IDENTITY = "iPhone Developer"; 513 | CODE_SIGN_STYLE = Automatic; 514 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 515 | DEVELOPMENT_TEAM = Z9KD6Z92BF; 516 | ENABLE_BITCODE = NO; 517 | FRAMEWORK_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "$(PROJECT_DIR)/Flutter", 520 | ); 521 | INFOPLIST_FILE = Runner/Info.plist; 522 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 523 | LIBRARY_SEARCH_PATHS = ( 524 | "$(inherited)", 525 | "$(PROJECT_DIR)/Flutter", 526 | ); 527 | PRODUCT_BUNDLE_IDENTIFIER = com.weizhi.ios; 528 | PRODUCT_NAME = "$(TARGET_NAME)"; 529 | PROVISIONING_PROFILE_SPECIFIER = ""; 530 | VERSIONING_SYSTEM = "apple-generic"; 531 | }; 532 | name = Debug; 533 | }; 534 | 97C147071CF9000F007C117D /* Release */ = { 535 | isa = XCBuildConfiguration; 536 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 537 | buildSettings = { 538 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 539 | CODE_SIGN_IDENTITY = "iPhone Developer"; 540 | CODE_SIGN_STYLE = Automatic; 541 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 542 | DEVELOPMENT_TEAM = Z9KD6Z92BF; 543 | ENABLE_BITCODE = NO; 544 | FRAMEWORK_SEARCH_PATHS = ( 545 | "$(inherited)", 546 | "$(PROJECT_DIR)/Flutter", 547 | ); 548 | INFOPLIST_FILE = Runner/Info.plist; 549 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 550 | LIBRARY_SEARCH_PATHS = ( 551 | "$(inherited)", 552 | "$(PROJECT_DIR)/Flutter", 553 | ); 554 | PRODUCT_BUNDLE_IDENTIFIER = com.weizhi.ios; 555 | PRODUCT_NAME = "$(TARGET_NAME)"; 556 | PROVISIONING_PROFILE_SPECIFIER = ""; 557 | VERSIONING_SYSTEM = "apple-generic"; 558 | }; 559 | name = Release; 560 | }; 561 | /* End XCBuildConfiguration section */ 562 | 563 | /* Begin XCConfigurationList section */ 564 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 565 | isa = XCConfigurationList; 566 | buildConfigurations = ( 567 | 97C147031CF9000F007C117D /* Debug */, 568 | 97C147041CF9000F007C117D /* Release */, 569 | 249021D3217E4FDB00AE95B9 /* Profile */, 570 | ); 571 | defaultConfigurationIsVisible = 0; 572 | defaultConfigurationName = Release; 573 | }; 574 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 575 | isa = XCConfigurationList; 576 | buildConfigurations = ( 577 | 97C147061CF9000F007C117D /* Debug */, 578 | 97C147071CF9000F007C117D /* Release */, 579 | 249021D4217E4FDB00AE95B9 /* Profile */, 580 | ); 581 | defaultConfigurationIsVisible = 0; 582 | defaultConfigurationName = Release; 583 | }; 584 | /* End XCConfigurationList section */ 585 | }; 586 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 587 | } 588 | --------------------------------------------------------------------------------