├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── flutter-analyze.yml │ └── main.yml ├── lib ├── assets │ ├── blank.mp3 │ ├── icon │ │ ├── icon.png │ │ ├── background.png │ │ └── foreground.png │ ├── kick │ │ ├── kickLogo.png │ │ └── badges │ │ │ ├── moderator.svg │ │ │ ├── verified.svg │ │ │ ├── vip.svg │ │ │ ├── founder.svg │ │ │ ├── sub_gifter.svg │ │ │ └── og.svg │ ├── twitchSmileEmoji.png │ ├── google-play-badge.png │ ├── twitch │ │ ├── twitch_logo.png │ │ ├── prediction-blue.svg │ │ ├── prediction-pink.svg │ │ └── prediction.svg │ ├── streamelements │ │ └── seLogo.png │ ├── youtube │ │ └── youtubeLogo.png │ ├── irl_link_dark_trans_logo.png │ ├── icon-github.svg │ └── discord-mark-blue.svg ├── src │ ├── core │ │ ├── usecases │ │ │ └── usecase.dart │ │ ├── utils │ │ │ ├── convert_to_device_timezone.dart │ │ │ ├── print_duration.dart │ │ │ ├── string_casing_extension.dart │ │ │ ├── list_move.dart │ │ │ ├── constants.dart │ │ │ ├── crashlytics_talker_observer.dart │ │ │ ├── username_color.dart │ │ │ ├── pkce_utils.dart │ │ │ └── init_dio.dart │ │ ├── failure.dart │ │ ├── params │ │ │ ├── streamelements_auth_params.dart │ │ │ ├── twitch_auth_params.dart │ │ │ └── kick_auth_params.dart │ │ ├── services │ │ │ ├── app_info_service.dart │ │ │ ├── talker_service.dart │ │ │ └── settings_service.dart │ │ └── resources │ │ │ └── app_translations.dart │ ├── domain │ │ ├── entities │ │ │ ├── stream_elements │ │ │ │ ├── se_overlay.dart │ │ │ │ ├── se_me.dart │ │ │ │ ├── se_credentials.dart │ │ │ │ └── se_song.dart │ │ │ ├── kick │ │ │ │ ├── kick_category.dart │ │ │ │ ├── kick_user.dart │ │ │ │ ├── kick_credentials.dart │ │ │ │ └── kick_channel.dart │ │ │ ├── settings │ │ │ │ ├── obs_settings.dart │ │ │ │ ├── hidden_user.dart │ │ │ │ ├── chat_settings.dart │ │ │ │ ├── general_settings.dart │ │ │ │ ├── browser_tab_settings.dart │ │ │ │ └── chat_events_settings.dart │ │ │ ├── twitch │ │ │ │ ├── twitch_decoded_idtoken.dart │ │ │ │ ├── twitch_user.dart │ │ │ │ ├── twitch_credentials.dart │ │ │ │ ├── twitch_poll.dart │ │ │ │ ├── twitch_hype_train.dart │ │ │ │ └── twitch_prediction.dart │ │ │ └── rtmp.dart │ │ ├── repositories │ │ │ └── rtmp_repository.dart │ │ └── usecases │ │ │ ├── rtmp │ │ │ ├── delete_rtmp_usecase.dart │ │ │ ├── add_rtmp_usecase.dart │ │ │ ├── get_rtmp_list_usecase.dart │ │ │ ├── update_rtmp_usecase.dart │ │ │ └── get_rtmp_by_id_usecase.dart │ │ │ ├── kick │ │ │ ├── logout_usecase.dart │ │ │ ├── get_kick_local_usecase.dart │ │ │ ├── get_kick_channels_usecase.dart │ │ │ ├── kick_refresh_token_usecase.dart │ │ │ ├── login_usecase.dart │ │ │ ├── unban_kick_user_usecase.dart │ │ │ ├── post_kick_chat_nessage_usecase.dart │ │ │ ├── ban_kick_user_usecase.dart │ │ │ ├── get_kick_categories_usecase.dart │ │ │ └── patch_kick_channel_usecase.dart │ │ │ ├── twitch │ │ │ ├── logout_usecase.dart │ │ │ ├── get_twitch_local_usecase.dart │ │ │ ├── refresh_token_usecase.dart │ │ │ ├── login_usecase.dart │ │ │ ├── get_recent_messages.dart │ │ │ ├── get_twitch_user_usecase.dart │ │ │ ├── get_twitch_users_usecase.dart │ │ │ ├── set_stream_title_usecase.dart │ │ │ ├── get_stream_info_usecase.dart │ │ │ ├── delete_message_usecase.dart │ │ │ ├── create_poll_usecase.dart │ │ │ ├── ban_user_usecase.dart │ │ │ ├── end_poll_usecase.dart │ │ │ ├── set_chat_settings_usecase.dart │ │ │ └── end_prediction_usecase.dart │ │ │ ├── settings │ │ │ ├── get_settings_usecase.dart │ │ │ ├── set_settings_usecase.dart │ │ │ ├── add_chat_group_usecase.dart │ │ │ ├── add_hidden_user_usecase.dart │ │ │ ├── get_hidden_users_usecase.dart │ │ │ ├── edit_browser_tab_usecase.dart │ │ │ ├── get_chats_groups_usecase.dart │ │ │ ├── remove_chat_group_usecase.dart │ │ │ ├── remove_hidden_user_usecase.dart │ │ │ ├── add_browser_tab_usecase.dart │ │ │ ├── get_browser_tabs_usecase.dart │ │ │ ├── remove_browser_tab_usecase.dart │ │ │ ├── add_channel_usecase.dart │ │ │ └── remove_channel_usecase.dart │ │ │ ├── streamelements │ │ │ ├── disconnect_usecase.dart │ │ │ ├── get_se_settings_usecase.dart │ │ │ ├── set_se_settings_usecase.dart │ │ │ ├── refresh_token_usecase.dart │ │ │ ├── get_local_credentials_usecase.dart │ │ │ ├── login_usecase.dart │ │ │ ├── get_me_usecase.dart │ │ │ ├── next_song_usecase.dart │ │ │ ├── reset_queue_usecase.dart │ │ │ ├── remove_song_usecase.dart │ │ │ ├── get_overlays_usecase.dart │ │ │ ├── get_song_queue_usecase.dart │ │ │ ├── get_song_playing_usecase.dart │ │ │ ├── replay_activity_usecase.dart │ │ │ ├── update_player_state_usecase.dart │ │ │ └── get_last_activities_usecase.dart │ │ │ ├── dashboard │ │ │ ├── add_dashboard_event_usecase.dart │ │ │ ├── delete_dashboard_event_usecase.dart │ │ │ └── get_dashboard_events_usecase.dart │ │ │ ├── tts │ │ │ ├── get_tts_settings_usecase.dart │ │ │ └── set_tts_settings_usecase.dart │ │ │ └── obs │ │ │ ├── get_obs_credentials_usecase.dart │ │ │ └── update_obs_url_usecase.dart │ ├── data │ │ └── entities │ │ │ ├── stream_elements │ │ │ ├── se_overlay_dto.dart │ │ │ ├── se_me_dto.dart │ │ │ ├── se_overlay_dto.g.dart │ │ │ ├── se_credentials_dto.dart │ │ │ ├── se_me_dto.g.dart │ │ │ └── se_credentials_dto.g.dart │ │ │ ├── rtmp_dto.dart │ │ │ ├── obs_settings_dto.dart │ │ │ ├── twitch │ │ │ ├── twitch_decoded_idtoken_dto.dart │ │ │ ├── twitch_poll_dto.g.dart │ │ │ ├── twitch_decoded_idtoken_dto.g.dart │ │ │ ├── twitch_hype_train_dto.g.dart │ │ │ ├── twitch_prediction_dto.g.dart │ │ │ ├── twitch_user_dto.g.dart │ │ │ ├── twitch_user_dto.dart │ │ │ └── twitch_credentials_dto.g.dart │ │ │ ├── kick │ │ │ ├── kick_category_dto.dart │ │ │ ├── kick_user_dto.dart │ │ │ ├── kick_credentials_dto.dart │ │ │ ├── kick_category_dto.g.dart │ │ │ ├── kick_user_dto.g.dart │ │ │ └── kick_credentials_dto.g.dart │ │ │ ├── settings │ │ │ ├── hidden_user_dto.dart │ │ │ ├── chat_events_settings_dto.dart │ │ │ ├── general_settings_dto.dart │ │ │ ├── hidden_user_dto.g.dart │ │ │ ├── stream_elements_settings_dto.dart │ │ │ ├── tts_settings_dto.dart │ │ │ ├── browser_tab_settings_dto.g.dart │ │ │ ├── browser_tab_settings_dto.dart │ │ │ ├── chat_settings_dto.dart │ │ │ ├── chat_events_settings_dto.g.dart │ │ │ └── general_settings_dto.g.dart │ │ │ ├── obs_settings_dto.g.dart │ │ │ ├── rtmp_dto.g.dart │ │ │ ├── settings_dto.dart │ │ │ └── settings_dto.g.dart │ ├── presentation │ │ ├── views │ │ │ └── settings │ │ │ │ └── talker_screen.dart │ │ ├── widgets │ │ │ ├── chats │ │ │ │ └── chat_message │ │ │ │ │ ├── shared │ │ │ │ │ ├── timestamp.dart │ │ │ │ │ └── third_part_emote.dart │ │ │ │ │ ├── kick │ │ │ │ │ └── kick_emote.dart │ │ │ │ │ └── twitch │ │ │ │ │ ├── twitch_emote.dart │ │ │ │ │ └── cheer_emote.dart │ │ │ ├── premium_feature_badge.dart │ │ │ ├── alert_message.dart │ │ │ └── hype_train.dart │ │ └── controllers │ │ │ └── settings │ │ │ └── hidden_users_settings_controller.dart │ └── bindings │ │ └── settings │ │ ├── hidden_users_bindings.dart │ │ └── obs_settings_bindings.dart └── routes │ └── app_routes.dart ├── test ├── deeplink_test_apple.sh ├── deeplink_test_android.sh ├── twitch_cli_api_test.sh ├── obs_test.sh ├── widget_test.dart └── twitch_cli_events_test.sh ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchBackground.imageset │ │ │ ├── background.png │ │ │ └── Contents.json │ └── Runner.entitlements ├── watchkitapp Watch App │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 102.png │ │ │ ├── 108.png │ │ │ ├── 172.png │ │ │ ├── 196.png │ │ │ ├── 216.png │ │ │ ├── 234.png │ │ │ ├── 258.png │ │ │ ├── 48.png │ │ │ ├── 55.png │ │ │ ├── 58.png │ │ │ ├── 66.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ ├── 88.png │ │ │ ├── 92.png │ │ │ └── 1024.png │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── watchkitappApp.swift ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── firebase_app_id_file.json ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── watchkitapp-Watch-App-Info.plist ├── RunnerTests │ └── RunnerTests.swift ├── .gitignore └── ci_scripts │ └── ci_post_clone.sh ├── android ├── app │ └── src │ │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── service_icon.png │ │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_bg_service_small.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_bg_service_small.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_bg_service_small.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_bg_service_small.png │ │ │ │ ├── ic_launcher_background.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ic_launcher_background.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── launcher_icon.xml │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ ├── values │ │ │ │ └── styles.xml │ │ │ └── values-night │ │ │ │ └── styles.xml │ │ └── kotlin │ │ │ └── dev │ │ │ └── lezd │ │ │ └── www │ │ │ └── irllink │ │ │ └── MainActivity.kt │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── profile │ │ └── AndroidManifest.xml │ │ └── androidTest │ │ └── java │ │ └── dev │ │ └── lezd │ │ └── www │ │ └── irllink │ │ └── MainActivityTest.java ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── firebase_app_id_file.json ├── .gitignore ├── build.gradle └── settings.gradle ├── devtools_options.yaml ├── .vscode ├── settings.json ├── tasks.json └── launch.json ├── .cursorignore ├── scripts └── install-hooks.bash ├── PRIVACY_POLICY.md ├── integration_test └── app_test.dart ├── .metadata ├── .gitignore └── flutter_launcher_icons.yaml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: LezdCS -------------------------------------------------------------------------------- /lib/assets/blank.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/blank.mp3 -------------------------------------------------------------------------------- /lib/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/icon/icon.png -------------------------------------------------------------------------------- /lib/assets/kick/kickLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/kick/kickLogo.png -------------------------------------------------------------------------------- /lib/assets/icon/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/icon/background.png -------------------------------------------------------------------------------- /lib/assets/icon/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/icon/foreground.png -------------------------------------------------------------------------------- /lib/assets/twitchSmileEmoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/twitchSmileEmoji.png -------------------------------------------------------------------------------- /test/deeplink_test_apple.sh: -------------------------------------------------------------------------------- 1 | /usr/bin/xcrun simctl openurl booted "irllink://www.irllink.com/#/book/hello-world" -------------------------------------------------------------------------------- /lib/assets/google-play-badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/google-play-badge.png -------------------------------------------------------------------------------- /lib/assets/twitch/twitch_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/twitch/twitch_logo.png -------------------------------------------------------------------------------- /lib/assets/streamelements/seLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/streamelements/seLogo.png -------------------------------------------------------------------------------- /lib/assets/youtube/youtubeLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/youtube/youtubeLogo.png -------------------------------------------------------------------------------- /lib/assets/irl_link_dark_trans_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/lib/assets/irl_link_dark_trans_logo.png -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | #import -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/deeplink_test_android.sh: -------------------------------------------------------------------------------- 1 | adb shell am start -a android.intent.action.VIEW \ 2 | -d "irllink://com.irllink.app/#/book/hello-world" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/service_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable/service_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/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/LezdCS/irl-link/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/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/twitch_cli_api_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Generate the mock-api data 4 | twitch mock-api generate 5 | 6 | # Start the mock-api server 7 | twitch mock-api start 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_bg_service_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-hdpi/ic_bg_service_small.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_bg_service_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-mdpi/ic_bg_service_small.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_bg_service_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xhdpi/ic_bg_service_small.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_bg_service_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_bg_service_small.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4608m 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | ; org.gradle.parallel=true 5 | ; org.gradle.daemon=true 6 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/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/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/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/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/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/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/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/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/102.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/108.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/234.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/234.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/258.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/258.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/66.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/92.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LezdCS/irl-link/HEAD/ios/watchkitapp Watch App/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /test/obs_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Launch OBS Studio 4 | open -a "OBS" & 5 | 6 | # Run ngrok in a new terminal window 7 | osascript -e 'tell app "Terminal" to do script "ngrok http 4455"' -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | testWidgets('todo', (WidgetTester tester) async { 5 | expect(true, true); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/dev/lezd/www/irllink/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.lezd.www.irllink 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/core/usecases/usecase.dart: -------------------------------------------------------------------------------- 1 | // This abstract class is intentionally defined with a single member to allow for generic use cases. 2 | // ignore: one_member_abstracts 3 | abstract class UseCase { 4 | Future call({required P params}); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/core/utils/convert_to_device_timezone.dart: -------------------------------------------------------------------------------- 1 | import 'package:instant/instant.dart'; 2 | 3 | DateTime convertToDeviceTimezone(DateTime date) { 4 | return dateTimeToZone( 5 | zone: DateTime.now().timeZoneName, 6 | datetime: date, 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /lib/src/domain/entities/stream_elements/se_overlay.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class SeOverlay { 5 | final String id; 6 | final String name; 7 | 8 | const SeOverlay({ 9 | required this.id, 10 | required this.name, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Nov 16 18:13:09 JST 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 4 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig" -------------------------------------------------------------------------------- /ios/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:1012029402006:ios:b0dc75db1bc354e51bcbb7", 5 | "FIREBASE_PROJECT_ID": "irl-link", 6 | "GCM_SENDER_ID": "1012029402006" 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB", 3 | "lldb.launch.expressions": "native", 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": "explicit", 6 | "source.organizeImports": "explicit" 7 | }, 8 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/firebase_app_id_file.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_generated_by": "FlutterFire CLI", 3 | "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", 4 | "GOOGLE_APP_ID": "1:1012029402006:android:850b5bda777692251bcbb7", 5 | "FIREBASE_PROJECT_ID": "irl-link", 6 | "GCM_SENDER_ID": "1012029402006" 7 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/watchkitapp-Watch-App-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIconFiles 6 | 7 | AppIcon-40x40 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.cursorignore: -------------------------------------------------------------------------------- 1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) 2 | android/app/build/ 3 | android/build/ios 4 | android/app/src/main/assets/ 5 | android/app/src/main/res/ 6 | android/app/src/main/res/values/ 7 | android/app/src/main/res/values-v21/ 8 | android/app/src/main/res/values-v22/ 9 | android/app/src/main/res/values-v23/ -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | 13 | *.cxx 14 | app/.cxx/* -------------------------------------------------------------------------------- /lib/src/domain/entities/kick/kick_category.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class KickCategory { 5 | final int id; 6 | final String name; 7 | final String thumbnail; 8 | 9 | const KickCategory({ 10 | required this.id, 11 | required this.name, 12 | required this.thumbnail, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/domain/entities/settings/obs_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class ObsSettings { 5 | final String url; 6 | final String password; 7 | final bool isConnected; 8 | 9 | const ObsSettings({ 10 | required this.url, 11 | required this.password, 12 | required this.isConnected, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/domain/entities/twitch/twitch_decoded_idtoken.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class TwitchDecodedIdToken { 5 | final String preferredUsername; 6 | final String profilePicture; 7 | 8 | const TwitchDecodedIdToken({ 9 | required this.preferredUsername, 10 | required this.profilePicture, 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/assets/kick/badges/moderator.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /ios/watchkitapp Watch App/watchkitappApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // watchkitappApp.swift 3 | // watchkitapp Watch App 4 | // 5 | // Created by Julien on 2024/09/10. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct watchkitapp_Watch_AppApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/domain/entities/settings/hidden_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:irllink/src/domain/entities/chat/chat_message.dart' 2 | show Platform; 3 | 4 | class HiddenUser { 5 | final String id; 6 | final String username; 7 | final Platform platform; 8 | 9 | HiddenUser({ 10 | required this.id, 11 | required this.username, 12 | required this.platform, 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "pub" # See documentation for possible values 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "weekly" 10 | target-branch: "develop" -------------------------------------------------------------------------------- /lib/src/domain/entities/stream_elements/se_me.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class SeMe { 5 | final String id; 6 | final String avatar; 7 | final String username; 8 | final String displayName; 9 | 10 | const SeMe({ 11 | required this.id, 12 | required this.avatar, 13 | required this.username, 14 | required this.displayName, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/core/utils/print_duration.dart: -------------------------------------------------------------------------------- 1 | // Print duration in HH:MM:SS format 2 | String printDuration(Duration duration) { 3 | String twoDigits(int n) => n.toString().padLeft(2, "0"); 4 | String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); 5 | String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); 6 | return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds"; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/domain/entities/kick/kick_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class KickUser { 5 | final int userId; 6 | final String name; 7 | final String email; 8 | final String profilePicture; 9 | 10 | const KickUser({ 11 | required this.userId, 12 | required this.name, 13 | required this.email, 14 | required this.profilePicture, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /scripts/install-hooks.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # To link the hook correctly, run this command at the root of the project: 4 | # chmod +x scripts/*.bash && scripts/install-hooks.bash 5 | 6 | GIT_DIR=$(git rev-parse --git-dir) 7 | 8 | echo "Installing hooks..." 9 | # this command creates symlink to our pre-commit script 10 | ln -s ../../scripts/pre-commit.bash $GIT_DIR/hooks/pre-commit 11 | echo "Done!" -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /lib/assets/twitch/prediction-blue.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /lib/assets/twitch/prediction-pink.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.associated-domains 8 | 9 | applinks:irllink.com 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Start Custom Script", 6 | "type": "shell", 7 | "command": "./test/obs_test.sh", 8 | "problemMatcher": [], 9 | "presentation": { 10 | "echo": true, 11 | "reveal": "never", 12 | "focus": false, 13 | "panel": "shared" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/entities/rtmp.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | @immutable 4 | class Rtmp { 5 | final int id; 6 | final String name; 7 | final String url; 8 | final String key; 9 | final DateTime createdAt; 10 | 11 | const Rtmp({ 12 | required this.id, 13 | required this.name, 14 | required this.url, 15 | required this.key, 16 | required this.createdAt, 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/domain/entities/stream_elements/se_credentials.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class SeCredentials { 5 | final String accessToken; 6 | final String refreshToken; 7 | final int expiresIn; 8 | final String scopes; 9 | 10 | const SeCredentials({ 11 | required this.accessToken, 12 | required this.refreshToken, 13 | required this.expiresIn, 14 | required this.scopes, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/core/failure.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:irllink/src/core/services/talker_service.dart'; 3 | 4 | class Failure { 5 | String message; 6 | Failure(this.message) { 7 | Get.find().talker.error(message); 8 | } 9 | } 10 | 11 | class DatabaseFailure extends Failure { 12 | DatabaseFailure() : super('Database operation failed'); 13 | } 14 | 15 | class NotFoundFailure extends Failure { 16 | NotFoundFailure() : super('Resource not found'); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/core/utils/string_casing_extension.dart: -------------------------------------------------------------------------------- 1 | extension StringCasingExtension on String { 2 | // Capitalize the first letter of the string 3 | String toCapitalized() => 4 | length > 0 ? '${this[0].toUpperCase()}${substring(1).toLowerCase()}' : ''; 5 | 6 | // Convert the string to title case (capitalize the first letter of each word) 7 | String toTitleCase() => replaceAll(RegExp(' +'), ' ') 8 | .split(' ') 9 | .map((str) => str.toCapitalized()) 10 | .join(' '); 11 | } 12 | -------------------------------------------------------------------------------- /lib/assets/twitch/prediction.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/domain/repositories/rtmp_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/domain/entities/rtmp.dart'; 4 | 5 | abstract class RtmpRepository { 6 | Future>> getRtmpList(); 7 | Future> getRtmpById(int id); 8 | Future> addRtmp(Rtmp rtmp); 9 | Future> updateRtmp(Rtmp rtmp); 10 | Future> deleteRtmp(int id); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/data/entities/stream_elements/se_overlay_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'se_overlay_dto.freezed.dart'; 4 | part 'se_overlay_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class SeOverlayDTO with _$SeOverlayDTO { 8 | const factory SeOverlayDTO({ 9 | @JsonKey(name: '_id') required String id, 10 | required String name, 11 | }) = _SeOverlayDTO; 12 | 13 | factory SeOverlayDTO.fromJson(Map json) => 14 | _$SeOverlayDTOFromJson(json); 15 | } 16 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Flutter Debug with Script", 6 | "request": "launch", 7 | "type": "dart", 8 | "program": "lib/main.dart", 9 | "preLaunchTask": "Start Custom Script" 10 | }, 11 | { 12 | "name": "Flutter Debug without Script", 13 | "request": "launch", 14 | "type": "dart", 15 | "program": "lib/main.dart", 16 | "preLaunchTask": "" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/src/core/utils/list_move.dart: -------------------------------------------------------------------------------- 1 | extension MoveElement on List { 2 | /// Moves the element of the list at the given [from] index to the [to] index. 3 | void move(int from, int to) { 4 | RangeError.checkValidIndex(from, this, "from", length); 5 | RangeError.checkValidIndex(to, this, "to", length); 6 | var element = this[from]; 7 | if (from < to) { 8 | setRange(from, to, this, from + 1); 9 | } else { 10 | setRange(to + 1, from + 1, this, to); 11 | } 12 | this[to] = element; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/data/entities/rtmp_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'rtmp_dto.freezed.dart'; 4 | part 'rtmp_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class RtmpDTO with _$RtmpDTO { 8 | const factory RtmpDTO({ 9 | required int id, 10 | required String name, 11 | required String url, 12 | required String key, 13 | required DateTime createdAt, 14 | }) = _RtmpDTO; 15 | 16 | factory RtmpDTO.fromJson(Map json) => 17 | _$RtmpDTOFromJson(json); 18 | } 19 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/assets/kick/badges/verified.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | abstract class Routes { 2 | static const home = '/home'; 3 | static const login = '/login'; 4 | static const settings = '/settings'; 5 | static const rtmpSettings = '/rtmp-settings'; 6 | static const streamelementsSettings = '/streamelements-settings'; 7 | static const browserSettings = '/browser-settings'; 8 | static const tabs = '/tabs'; 9 | static const hiddenUsersSettings = '/hidden-users-settings'; 10 | static const chatsSettings = '/chats-settings'; 11 | static const obsSettings = '/obs-settings'; 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/data/entities/stream_elements/se_me_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'se_me_dto.freezed.dart'; 4 | part 'se_me_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class SeMeDTO with _$SeMeDTO { 8 | const factory SeMeDTO({ 9 | @JsonKey(name: '_id') required String id, 10 | required String avatar, 11 | required String username, 12 | required String displayName, 13 | }) = _SeMeDTO; 14 | 15 | factory SeMeDTO.fromJson(Map json) => 16 | _$SeMeDTOFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/core/params/streamelements_auth_params.dart: -------------------------------------------------------------------------------- 1 | import 'package:irllink/src/core/utils/constants.dart'; 2 | 3 | class StreamelementsAuthParams { 4 | final String clientId; 5 | final String redirectUri; 6 | final String responseType; 7 | final String scopes; 8 | 9 | const StreamelementsAuthParams({ 10 | this.clientId = kStreamelementsAuthClientId, 11 | this.redirectUri = 'https://www.irllink.com/api/streamelements/auth', 12 | this.responseType = 'code', 13 | this.scopes = 'channel:read tips:read activities:read overlays:read', 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/data/entities/obs_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'obs_settings_dto.freezed.dart'; 4 | part 'obs_settings_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class ObsSettingsDTO with _$ObsSettingsDTO { 8 | const factory ObsSettingsDTO({ 9 | required String url, 10 | required String password, 11 | @JsonKey(name: 'is_connected') required bool isConnected, 12 | }) = _ObsSettingsDTO; 13 | 14 | factory ObsSettingsDTO.fromJson(Map json) => 15 | _$ObsSettingsDTOFromJson(json); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/domain/entities/kick/kick_credentials.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:irllink/src/domain/entities/kick/kick_user.dart'; 3 | 4 | @immutable 5 | class KickCredentials { 6 | final String accessToken; 7 | final String refreshToken; 8 | final int expiresIn; 9 | final KickUser kickUser; 10 | final String scopes; 11 | 12 | const KickCredentials({ 13 | required this.accessToken, 14 | required this.refreshToken, 15 | required this.expiresIn, 16 | required this.kickUser, 17 | required this.scopes, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/core/services/app_info_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:package_info_plus/package_info_plus.dart'; 3 | 4 | class AppInfoService extends GetxService { 5 | late PackageInfo _packageInfo; 6 | 7 | Future init() async { 8 | _packageInfo = await PackageInfo.fromPlatform(); 9 | return this; 10 | } 11 | 12 | String get appName => _packageInfo.appName; 13 | String get version => _packageInfo.version; 14 | String get packageName => _packageInfo.packageName; 15 | String get buildNumber => _packageInfo.buildNumber; 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/rtmp/delete_rtmp_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/rtmp_repository.dart'; 5 | 6 | class DeleteRtmpUseCase implements UseCase, int> { 7 | final RtmpRepository rtmpRepository; 8 | 9 | DeleteRtmpUseCase(this.rtmpRepository); 10 | 11 | @override 12 | Future> call({required int params}) { 13 | return rtmpRepository.deleteRtmp(params); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | IRL Link: Privacy policy 2 | 3 | This is an open source Flutter application. The source code is available on GitHub under the GPL-3.0 license; the app is also available on Google Play and the App Store. 4 | 5 | The only information sent to our own server is your Twitch access token so you can log in to the application. 6 | If you setup the RealtimeIRL module, you will be asked to let the application access your location. By using the RealtimeIRL module, you aknowledge the application to send your current location to RealtimeIRL services. 7 | 8 | For any questions and claims: 9 | support@irllink.com 10 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_decoded_idtoken_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'twitch_decoded_idtoken_dto.freezed.dart'; 4 | part 'twitch_decoded_idtoken_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class TwitchDecodedIdTokenDTO with _$TwitchDecodedIdTokenDTO { 8 | const factory TwitchDecodedIdTokenDTO({ 9 | required String preferredUsername, 10 | required String profilePicture, 11 | }) = _TwitchDecodedIdTokenDTO; 12 | 13 | factory TwitchDecodedIdTokenDTO.fromJson(Map json) => 14 | _$TwitchDecodedIdTokenDTOFromJson(json); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/logout_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 5 | 6 | class LogoutKickUseCase implements UseCase, String> { 7 | final KickRepository kickRepository; 8 | 9 | LogoutKickUseCase(this.kickRepository); 10 | 11 | @override 12 | Future> call({ 13 | required String params, 14 | }) { 15 | return kickRepository.logout(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/logout_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 5 | 6 | class LogoutUseCase implements UseCase, String> { 7 | final TwitchRepository twitchRepository; 8 | 9 | LogoutUseCase(this.twitchRepository); 10 | 11 | @override 12 | Future> call({ 13 | required String params, 14 | }) { 15 | return twitchRepository.logout(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/data/entities/kick/kick_category_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'kick_category_dto.freezed.dart'; 4 | part 'kick_category_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class KickCategoryDTO with _$KickCategoryDTO { 8 | const factory KickCategoryDTO({ 9 | @JsonKey(name: 'id') required int id, 10 | @JsonKey(name: 'name') required String name, 11 | @JsonKey(name: 'thumbnail') required String thumbnail, 12 | }) = _KickCategoryDTO; 13 | 14 | factory KickCategoryDTO.fromJson(Map json) => 15 | _$KickCategoryDTOFromJson(json); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/domain/entities/twitch/twitch_user.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class TwitchUser { 5 | final String id; 6 | final String login; 7 | final String displayName; 8 | final String broadcasterType; 9 | final String description; 10 | final String profileImageUrl; 11 | final int viewCount; 12 | 13 | const TwitchUser({ 14 | required this.id, 15 | required this.login, 16 | required this.displayName, 17 | required this.broadcasterType, 18 | required this.description, 19 | required this.profileImageUrl, 20 | required this.viewCount, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/hidden_user_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:irllink/src/domain/entities/chat/chat_message.dart' 3 | show Platform; 4 | 5 | part 'hidden_user_dto.freezed.dart'; 6 | part 'hidden_user_dto.g.dart'; 7 | 8 | @freezed 9 | abstract class HiddenUserDTO with _$HiddenUserDTO { 10 | const factory HiddenUserDTO({ 11 | required String id, 12 | required String username, 13 | required Platform platform, 14 | }) = _HiddenUserDTO; 15 | 16 | factory HiddenUserDTO.fromJson(Map json) => 17 | _$HiddenUserDTOFromJson(json); 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/core/utils/constants.dart: -------------------------------------------------------------------------------- 1 | const kTwitchAuthClientId = 'shuf0nmdw2ao8o3rw1xe4raodb9ug3'; 2 | const kTwitchAuthUrlBase = 'id.twitch.tv'; 3 | const kTwitchAuthUrlPath = '/oauth2/authorize'; 4 | const kTwitchApiUrlBase = 'https://api.twitch.tv'; 5 | 6 | const kRedirectScheme = 'dev.lezd.www.irllink'; 7 | 8 | const kStreamelementsAuthClientId = '77746eebf069856d'; 9 | const kStreamelementsUrlBase = 'https://api.streamelements.com'; 10 | 11 | const kKickAuthClientId = '01JRWDBTA5ZVQMX975KYEH07Q8'; 12 | const kKickAuthUrlBase = 'id.kick.com'; 13 | const kKickAuthUrlPath = '/oauth/authorize'; 14 | const kKickApiUrlBase = 'https://api.kick.com'; 15 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/rtmp/add_rtmp_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/rtmp.dart'; 5 | import 'package:irllink/src/domain/repositories/rtmp_repository.dart'; 6 | 7 | class AddRtmpUseCase implements UseCase, Rtmp> { 8 | final RtmpRepository rtmpRepository; 9 | 10 | AddRtmpUseCase(this.rtmpRepository); 11 | 12 | @override 13 | Future> call({required Rtmp params}) { 14 | return rtmpRepository.addRtmp(params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/assets/kick/badges/vip.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /lib/src/presentation/views/settings/talker_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:talker_flutter/talker_flutter.dart'; 3 | 4 | class TalkerScreenView extends StatelessWidget { 5 | final Talker talker; 6 | 7 | const TalkerScreenView({ 8 | super.key, 9 | required this.talker, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return TalkerScreen( 15 | appBarTitle: 'Logs', 16 | talker: talker, 17 | theme: const TalkerScreenTheme( 18 | backgroundColor: Color(0xFF0e0e10), 19 | cardColor: Color(0xFF18181b), 20 | ), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/data/entities/kick/kick_user_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'kick_user_dto.freezed.dart'; 4 | part 'kick_user_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class KickUserDTO with _$KickUserDTO { 8 | const factory KickUserDTO({ 9 | @JsonKey(name: 'user_id') required int userId, 10 | @JsonKey(name: 'name') required String name, 11 | @JsonKey(name: 'email') required String email, 12 | @JsonKey(name: 'profile_picture') required String profilePicture, 13 | }) = _KickUserDTO; 14 | 15 | factory KickUserDTO.fromJson(Map json) => 16 | _$KickUserDTOFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/rtmp/get_rtmp_list_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/rtmp.dart'; 5 | import 'package:irllink/src/domain/repositories/rtmp_repository.dart'; 6 | 7 | class GetRtmpListUseCase implements UseCase>, void> { 8 | final RtmpRepository rtmpRepository; 9 | 10 | GetRtmpListUseCase(this.rtmpRepository); 11 | 12 | @override 13 | Future>> call({void params}) { 14 | return rtmpRepository.getRtmpList(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/rtmp/update_rtmp_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/rtmp.dart'; 5 | import 'package:irllink/src/domain/repositories/rtmp_repository.dart'; 6 | 7 | class UpdateRtmpUseCase implements UseCase, Rtmp> { 8 | final RtmpRepository rtmpRepository; 9 | 10 | UpdateRtmpUseCase(this.rtmpRepository); 11 | 12 | @override 13 | Future> call({required Rtmp params}) { 14 | return rtmpRepository.updateRtmp(params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/rtmp/get_rtmp_by_id_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/rtmp.dart'; 5 | import 'package:irllink/src/domain/repositories/rtmp_repository.dart'; 6 | 7 | class GetRtmpByIdUseCase implements UseCase, int> { 8 | final RtmpRepository rtmpRepository; 9 | 10 | GetRtmpByIdUseCase(this.rtmpRepository); 11 | 12 | @override 13 | Future> call({required int params}) { 14 | return rtmpRepository.getRtmpById(params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/core/services/talker_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:irllink/src/core/utils/crashlytics_talker_observer.dart'; 3 | import 'package:talker_flutter/talker_flutter.dart'; 4 | 5 | class TalkerService extends GetxService { 6 | late Talker talker; 7 | 8 | Future init() async { 9 | final crashlyticsTalkerObserver = CrashlyticsTalkerObserver(); 10 | talker = TalkerFlutter.init( 11 | settings: TalkerSettings( 12 | colors: { 13 | TalkerKey.debug: AnsiPen()..yellow(), 14 | }, 15 | ), 16 | observer: crashlyticsTalkerObserver, 17 | ); 18 | return this; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/get_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetSettingsUseCase implements UseCase, void> { 8 | final SettingsRepository settingsRepository; 9 | 10 | GetSettingsUseCase(this.settingsRepository); 11 | 12 | @override 13 | Future> call({void params}) { 14 | return settingsRepository.getSettings(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/chats/chat_message/shared/timestamp.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | class Timestamp extends StatelessWidget { 5 | final int timestamp; 6 | 7 | const Timestamp({ 8 | super.key, 9 | required this.timestamp, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Text( 15 | DateFormat.Hm().format(DateTime.fromMillisecondsSinceEpoch(timestamp)), 16 | textAlign: TextAlign.end, 17 | style: const TextStyle( 18 | color: Colors.grey, 19 | fontSize: 14, 20 | fontWeight: FontWeight.w400, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/assets/kick/badges/founder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/set_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class SetSettingsUseCase implements UseCase, Settings> { 8 | final SettingsRepository settingsRepository; 9 | 10 | SetSettingsUseCase(this.settingsRepository); 11 | 12 | @override 13 | Future> call({required Settings params}) { 14 | return settingsRepository.setSettings(params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/core/utils/crashlytics_talker_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 2 | import 'package:talker_flutter/talker_flutter.dart'; 3 | 4 | class CrashlyticsTalkerObserver extends TalkerObserver { 5 | CrashlyticsTalkerObserver(); 6 | 7 | @override 8 | void onError(TalkerError err) { 9 | FirebaseCrashlytics.instance.recordError( 10 | err.error, 11 | err.stackTrace, 12 | reason: err.message, 13 | ); 14 | } 15 | 16 | @override 17 | void onException(TalkerException err) { 18 | FirebaseCrashlytics.instance.recordError( 19 | err.exception, 20 | err.stackTrace, 21 | reason: err.message, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/data/entities/stream_elements/se_overlay_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'se_overlay_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _SeOverlayDTO _$SeOverlayDTOFromJson(Map json) => 10 | _SeOverlayDTO( 11 | id: json['_id'] as String, 12 | name: json['name'] as String, 13 | ); 14 | 15 | Map _$SeOverlayDTOToJson(_SeOverlayDTO instance) => 16 | { 17 | '_id': instance.id, 18 | 'name': instance.name, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/get_kick_local_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/kick/kick_credentials.dart'; 5 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 6 | 7 | class GetKickLocalUseCase 8 | implements UseCase, void> { 9 | final KickRepository kickRepository; 10 | 11 | GetKickLocalUseCase(this.kickRepository); 12 | 13 | @override 14 | Future> call({void params}) { 15 | return kickRepository.getKickFromLocal(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/disconnect_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 5 | 6 | class StreamElementsDisconnectUseCase 7 | implements UseCase, String> { 8 | final StreamelementsRepository streamelementsRepository; 9 | 10 | StreamElementsDisconnectUseCase({required this.streamelementsRepository}); 11 | 12 | @override 13 | Future> call({required String params}) { 14 | return streamelementsRepository.disconnect(params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/twitch_cli_events_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # Start the Twitch event websocket server 4 | twitch event websocket start-server & 5 | 6 | # Function to trigger events 7 | trigger_events() { 8 | while true; do 9 | # Trigger channel prediction begin event 10 | twitch event trigger channel.prediction.progress --transport=websocket 11 | # Trigger hype train begin event 12 | twitch event trigger channel.hype_train.begin --transport=websocket 13 | # Trigger channel poll begin event again 14 | twitch event trigger channel.poll.progress --transport=websocket 15 | # Wait for 5 seconds before the next iteration 16 | sleep 10 17 | done 18 | } 19 | 20 | # Execute the trigger events function 21 | trigger_events -------------------------------------------------------------------------------- /lib/src/data/entities/stream_elements/se_credentials_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'se_credentials_dto.freezed.dart'; 4 | part 'se_credentials_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class SeCredentialsDTO with _$SeCredentialsDTO { 8 | const factory SeCredentialsDTO({ 9 | @JsonKey(name: 'access_token') required String accessToken, 10 | @JsonKey(name: 'refresh_token') required String refreshToken, 11 | @JsonKey(name: 'expires_in') required int expiresIn, 12 | @JsonKey(name: 'scopes') required String scopes, 13 | }) = _SeCredentialsDTO; 14 | 15 | factory SeCredentialsDTO.fromJson(Map json) => 16 | _$SeCredentialsDTOFromJson(json); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/get_twitch_local_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_credentials.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class GetTwitchLocalUseCase 8 | implements UseCase, void> { 9 | final TwitchRepository twitchRepository; 10 | 11 | GetTwitchLocalUseCase(this.twitchRepository); 12 | 13 | @override 14 | Future> call({void params}) { 15 | return twitchRepository.getTwitchFromLocal(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/data/entities/kick/kick_credentials_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:irllink/src/data/entities/kick/kick_user_dto.dart'; 3 | 4 | part 'kick_credentials_dto.freezed.dart'; 5 | part 'kick_credentials_dto.g.dart'; 6 | 7 | @freezed 8 | abstract class KickCredentialsDTO with _$KickCredentialsDTO { 9 | const factory KickCredentialsDTO({ 10 | required String accessToken, 11 | required String refreshToken, 12 | required int expiresIn, 13 | required KickUserDTO kickUser, 14 | required String scopes, 15 | }) = _KickCredentialsDTO; 16 | 17 | factory KickCredentialsDTO.fromJson(Map json) => 18 | _$KickCredentialsDTOFromJson(json); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/entities/stream_elements/se_song.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class SeSong { 5 | final String id; 6 | final String videoId; 7 | final String title; 8 | final String channel; 9 | final int duration; 10 | 11 | const SeSong({ 12 | required this.id, 13 | required this.videoId, 14 | required this.title, 15 | required this.channel, 16 | required this.duration, 17 | }); 18 | 19 | factory SeSong.fromJson(Map map) { 20 | return SeSong( 21 | id: map["_id"] ?? '', 22 | title: map["title"], 23 | videoId: map["videoId"], 24 | duration: map["duration"] ?? 0, 25 | channel: map["channel"], 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/add_chat_group_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/chat_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class AddChatGroupUsecase implements UseCase, ChatGroup> { 8 | final SettingsRepository settingsRepository; 9 | 10 | AddChatGroupUsecase({required this.settingsRepository}); 11 | 12 | @override 13 | Future> call({required ChatGroup params}) async { 14 | return settingsRepository.addChatGroup(params); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | XCBuildData 18 | **/.generated/ 19 | Flutter/App.framework 20 | Flutter/Flutter.framework 21 | Flutter/Flutter.podspec 22 | Flutter/Generated.xcconfig 23 | Flutter/ephemeral/ 24 | Flutter/app.flx 25 | Flutter/app.zip 26 | Flutter/flutter_assets/ 27 | Flutter/flutter_export_environment.sh 28 | ServiceDefinitions.json 29 | Runner/GeneratedPluginRegistrant.* 30 | 31 | # Exceptions to above rules. 32 | !default.mode1v3 33 | !default.mode2v3 34 | !default.pbxuser 35 | !default.perspectivev3 36 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/dashboard/add_dashboard_event_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/dashboard_event.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class AddDashboardEventUseCase 8 | extends UseCase, DashboardEvent> { 9 | final SettingsRepository repository; 10 | 11 | AddDashboardEventUseCase({required this.repository}); 12 | 13 | @override 14 | Future> call({required DashboardEvent params}) async { 15 | return repository.addDashboardEvent(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/tts/get_tts_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/tts_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetTtsSettingsUsecase 8 | extends UseCase, void> { 9 | final SettingsRepository settingsRepository; 10 | 11 | GetTtsSettingsUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required void params}) async { 15 | return settingsRepository.getTtsSettings(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /integration_test/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:irllink/main.dart' as app; 5 | import 'package:patrol/patrol.dart'; 6 | 7 | void main() { 8 | Get.testMode = true; 9 | 10 | patrolTest('App test', ($) async { 11 | // Start app initialization 12 | final appInit = app.main(); 13 | 14 | // Wait for permission dialog and grant it 15 | // await $.native.grantPermissionWhenInUse(); 16 | 17 | // Wait for app initialization to complete 18 | await appInit; 19 | 20 | await $.pumpWidgetAndSettle(const app.Main()); 21 | expect($(const Key('maybe_later_key')), findsOneWidget); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/add_hidden_user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/hidden_user.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class AddHiddenUserUseCase 8 | implements UseCase, HiddenUser> { 9 | final SettingsRepository settingsRepository; 10 | 11 | AddHiddenUserUseCase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required HiddenUser params}) async { 15 | return settingsRepository.addHiddenUser(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/get_hidden_users_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/hidden_user.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetHiddenUsersUseCase 8 | implements UseCase>, void> { 9 | final SettingsRepository settingsRepository; 10 | 11 | GetHiddenUsersUseCase({required this.settingsRepository}); 12 | 13 | @override 14 | Future>> call({void params}) async { 15 | return settingsRepository.getHiddenUsers(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/dashboard/delete_dashboard_event_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/dashboard_event.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class DeleteDashboardEventUseCase 8 | extends UseCase, DashboardEvent> { 9 | final SettingsRepository repository; 10 | 11 | DeleteDashboardEventUseCase({required this.repository}); 12 | 13 | @override 14 | Future> call({required DashboardEvent params}) async { 15 | return repository.removeDashboardEvent(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/obs/get_obs_credentials_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/obs_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetObsCredentialsUsecase 8 | extends UseCase, void> { 9 | final SettingsRepository settingsRepository; 10 | 11 | GetObsCredentialsUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required void params}) async { 15 | return settingsRepository.getObsCredentials(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/obs/update_obs_url_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/obs_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class UpdateObsSettingsUsecase 8 | extends UseCase, ObsSettings> { 9 | final SettingsRepository settingsRepository; 10 | 11 | UpdateObsSettingsUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required ObsSettings params}) async { 15 | return settingsRepository.updateObsSettings(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/edit_browser_tab_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/browser_tab_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class EditBrowserTabUsecase 8 | implements UseCase, BrowserTab> { 9 | final SettingsRepository settingsRepository; 10 | 11 | EditBrowserTabUsecase(this.settingsRepository); 12 | 13 | @override 14 | Future> call({required BrowserTab params}) async { 15 | return settingsRepository.editBrowserTab(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/tts/set_tts_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/tts_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class SetTtsSettingsUsecase 8 | extends UseCase, TtsSettings> { 9 | final SettingsRepository settingsRepository; 10 | 11 | SetTtsSettingsUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required TtsSettings params}) async { 15 | return settingsRepository.updateTtsSettings(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/get_chats_groups_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/chat_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetChatGroupsUsecase 8 | implements UseCase>, void> { 9 | final SettingsRepository settingsRepository; 10 | 11 | GetChatGroupsUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future>> call({required void params}) async { 15 | return settingsRepository.getChatGroups(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/remove_chat_group_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/chat_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class RemoveChatGroupUsecase 8 | implements UseCase, ChatGroup> { 9 | final SettingsRepository settingsRepository; 10 | 11 | RemoveChatGroupUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required ChatGroup params}) async { 15 | return settingsRepository.removeChatGroup(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/remove_hidden_user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/hidden_user.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class RemoveHiddenUserUseCase 8 | implements UseCase, HiddenUser> { 9 | final SettingsRepository settingsRepository; 10 | 11 | RemoveHiddenUserUseCase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({required HiddenUser params}) async { 15 | return settingsRepository.removeHiddenUser(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_poll_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'twitch_poll_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _ChoiceDTO _$ChoiceDTOFromJson(Map json) => _ChoiceDTO( 10 | id: json['id'] as String, 11 | title: json['title'] as String, 12 | votes: (json['votes'] as num?)?.toInt() ?? 0, 13 | ); 14 | 15 | Map _$ChoiceDTOToJson(_ChoiceDTO instance) => 16 | { 17 | 'id': instance.id, 18 | 'title': instance.title, 19 | 'votes': instance.votes, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/add_browser_tab_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/browser_tab_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class AddBrowserTabUsecase 8 | implements UseCase, BrowserTab> { 9 | final SettingsRepository settingsRepository; 10 | 11 | AddBrowserTabUsecase(this.settingsRepository); 12 | 13 | @override 14 | Future> call({ 15 | required BrowserTab params, 16 | }) async { 17 | return settingsRepository.addBrowserTab(params); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/dashboard/get_dashboard_events_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/dashboard_event.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetDashboardEventsUseCase 8 | extends UseCase>, void> { 9 | final SettingsRepository repository; 10 | 11 | GetDashboardEventsUseCase({required this.repository}); 12 | 13 | @override 14 | Future>> call({ 15 | required void params, 16 | }) async { 17 | return repository.getDashboardEvents(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/core/utils/username_color.dart: -------------------------------------------------------------------------------- 1 | String randomUsernameColor(String username) { 2 | List> defaultColors = [ 3 | ["Red", "#FF0000"], 4 | ["Blue", "#0000FF"], 5 | ["Green", "#00FF00"], 6 | ["FireBrick", "#B22222"], 7 | ["Coral", "#FF7F50"], 8 | ["YellowGreen", "#9ACD32"], 9 | ["OrangeRed", "#FF4500"], 10 | ["SeaGreen", "#2E8B57"], 11 | ["GoldenRod", "#DAA520"], 12 | ["Chocolate", "#D2691E"], 13 | ["CadetBlue", "#5F9EA0"], 14 | ["DodgerBlue", "#1E90FF"], 15 | ["HotPink", "#FF69B4"], 16 | ["BlueViolet", "#8A2BE2"], 17 | ["SpringGreen", "#00FF7F"], 18 | ]; 19 | 20 | var n = username.codeUnitAt(0) + username.codeUnitAt(username.length - 1); 21 | return defaultColors[n % defaultColors.length][1]; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/get_browser_tabs_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/browser_tab_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class GetBrowserTabsUsecase 8 | implements UseCase>, void> { 9 | final SettingsRepository settingsRepository; 10 | 11 | GetBrowserTabsUsecase(this.settingsRepository); 12 | 13 | @override 14 | Future>> call({ 15 | required void params, 16 | }) async { 17 | return settingsRepository.getBrowserTabs(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/remove_browser_tab_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/browser_tab_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class RemoveBrowserTabUsecase 8 | implements UseCase, BrowserTab> { 9 | final SettingsRepository settingsRepository; 10 | 11 | RemoveBrowserTabUsecase(this.settingsRepository); 12 | 13 | @override 14 | Future> call({ 15 | required BrowserTab params, 16 | }) async { 17 | return settingsRepository.removeBrowserTab(params); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/get_kick_channels_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/kick/kick_channel.dart'; 5 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 6 | 7 | class GetKickChannelsUseCase 8 | implements UseCase>, String> { 9 | final KickRepository kickRepository; 10 | 11 | GetKickChannelsUseCase(this.kickRepository); 12 | 13 | @override 14 | Future>> call({ 15 | required String params, 16 | }) { 17 | return kickRepository.getChannels( 18 | accessToken: params, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/add_channel_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/chat_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class AddChannelUsecase 8 | implements UseCase, (ChatGroup, Channel)> { 9 | final SettingsRepository settingsRepository; 10 | 11 | AddChannelUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({ 15 | required (ChatGroup, Channel) params, 16 | }) async { 17 | return settingsRepository.addChannel(params.$1, params.$2); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/kick_refresh_token_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/kick/kick_credentials.dart'; 5 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 6 | 7 | class KickRefreshTokenUseCase 8 | implements UseCase, KickCredentials> { 9 | final KickRepository kickRepository; 10 | 11 | KickRefreshTokenUseCase(this.kickRepository); 12 | 13 | @override 14 | Future> call({ 15 | required KickCredentials params, 16 | }) { 17 | return kickRepository.refreshAccessToken( 18 | params, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/refresh_token_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_credentials.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class RefreshTwitchTokenUseCase 8 | implements UseCase, TwitchCredentials> { 9 | final TwitchRepository twitchRepository; 10 | 11 | RefreshTwitchTokenUseCase(this.twitchRepository); 12 | 13 | @override 14 | Future> call({ 15 | required TwitchCredentials params, 16 | }) { 17 | return twitchRepository.refreshAccessToken(params); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/flutter-analyze.yml: -------------------------------------------------------------------------------- 1 | name: Dart analyzer 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | analyze: 11 | runs-on: macos-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Flutter 17 | uses: subosito/flutter-action@v2 18 | 19 | - name: Enable Swift Package Manager 20 | run: flutter config --enable-swift-package-manager 21 | 22 | - name: Get Flutter dependencies 23 | run: flutter pub get 24 | 25 | - name: Run dart analyze 26 | uses: invertase/github-action-dart-analyzer@v3 27 | 28 | - name: Verify Dart formatting 29 | run: dart format --set-exit-if-changed . 30 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/settings/remove_channel_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/chat_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/settings_repository.dart'; 6 | 7 | class RemoveChannelUsecase 8 | implements UseCase, (ChatGroup, Channel)> { 9 | final SettingsRepository settingsRepository; 10 | 11 | RemoveChannelUsecase({required this.settingsRepository}); 12 | 13 | @override 14 | Future> call({ 15 | required (ChatGroup, Channel) params, 16 | }) async { 17 | return settingsRepository.removeChannel(params.$1, params.$2); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/login_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/params/kick_auth_params.dart'; 4 | import 'package:irllink/src/core/usecases/usecase.dart'; 5 | import 'package:irllink/src/domain/entities/kick/kick_credentials.dart'; 6 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 7 | 8 | class LoginKickUseCase 9 | implements UseCase, KickAuthParams> { 10 | final KickRepository kickRepository; 11 | 12 | LoginKickUseCase(this.kickRepository); 13 | 14 | @override 15 | Future> call({ 16 | required KickAuthParams params, 17 | }) { 18 | return kickRepository.getKickOauth(params); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/data/entities/kick/kick_category_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'kick_category_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _KickCategoryDTO _$KickCategoryDTOFromJson(Map json) => 10 | _KickCategoryDTO( 11 | id: (json['id'] as num).toInt(), 12 | name: json['name'] as String, 13 | thumbnail: json['thumbnail'] as String, 14 | ); 15 | 16 | Map _$KickCategoryDTOToJson(_KickCategoryDTO instance) => 17 | { 18 | 'id': instance.id, 19 | 'name': instance.name, 20 | 'thumbnail': instance.thumbnail, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_se_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/stream_elements_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class GetStreamElementsSettingsUseCase 8 | implements UseCase, void> { 9 | final StreamelementsRepository repository; 10 | 11 | GetStreamElementsSettingsUseCase({required this.repository}); 12 | 13 | @override 14 | Future> call({ 15 | void params, 16 | }) async { 17 | return repository.getStreamElementsSettings(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/data/entities/obs_settings_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'obs_settings_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _ObsSettingsDTO _$ObsSettingsDTOFromJson(Map json) => 10 | _ObsSettingsDTO( 11 | url: json['url'] as String, 12 | password: json['password'] as String, 13 | isConnected: json['is_connected'] as bool, 14 | ); 15 | 16 | Map _$ObsSettingsDTOToJson(_ObsSettingsDTO instance) => 17 | { 18 | 'url': instance.url, 19 | 'password': instance.password, 20 | 'is_connected': instance.isConnected, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/src/domain/entities/twitch/twitch_credentials.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:irllink/src/domain/entities/twitch/twitch_decoded_idtoken.dart'; 3 | import 'package:irllink/src/domain/entities/twitch/twitch_user.dart'; 4 | 5 | @immutable 6 | class TwitchCredentials { 7 | final String accessToken; 8 | final String idToken; 9 | final String refreshToken; 10 | final String expiresIn; 11 | final TwitchDecodedIdToken decodedIdToken; 12 | final TwitchUser twitchUser; 13 | final String scopes; 14 | 15 | const TwitchCredentials({ 16 | required this.accessToken, 17 | required this.idToken, 18 | required this.refreshToken, 19 | required this.expiresIn, 20 | required this.decodedIdToken, 21 | required this.twitchUser, 22 | required this.scopes, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/login_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/params/twitch_auth_params.dart'; 4 | import 'package:irllink/src/core/usecases/usecase.dart'; 5 | import 'package:irllink/src/domain/entities/twitch/twitch_credentials.dart'; 6 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 7 | 8 | class LoginUseCase 9 | implements UseCase, TwitchAuthParams> { 10 | final TwitchRepository twitchRepository; 11 | 12 | LoginUseCase(this.twitchRepository); 13 | 14 | @override 15 | Future> call({ 16 | required TwitchAuthParams params, 17 | }) { 18 | return twitchRepository.getTwitchOauth(params); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/assets/kick/badges/sub_gifter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/set_se_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/settings/stream_elements_settings.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class SetStreamElementsSettingsUseCase 8 | implements UseCase, StreamElementsSettings> { 9 | final StreamelementsRepository repository; 10 | 11 | SetStreamElementsSettingsUseCase({required this.repository}); 12 | 13 | @override 14 | Future> call({ 15 | required StreamElementsSettings params, 16 | }) async { 17 | return repository.updateStreamElementsSettings(params); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/refresh_token_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_credentials.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsRefreshTokenUseCase 8 | implements UseCase, SeCredentials> { 9 | final StreamelementsRepository streamelementsRepository; 10 | 11 | StreamElementsRefreshTokenUseCase({required this.streamelementsRepository}); 12 | 13 | @override 14 | Future> call({required SeCredentials params}) { 15 | return streamelementsRepository.refreshAccessToken(params); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/data/entities/stream_elements/se_me_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'se_me_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _SeMeDTO _$SeMeDTOFromJson(Map json) => _SeMeDTO( 10 | id: json['_id'] as String, 11 | avatar: json['avatar'] as String, 12 | username: json['username'] as String, 13 | displayName: json['displayName'] as String, 14 | ); 15 | 16 | Map _$SeMeDTOToJson(_SeMeDTO instance) => { 17 | '_id': instance.id, 18 | 'avatar': instance.avatar, 19 | 'username': instance.username, 20 | 'displayName': instance.displayName, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_local_credentials_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_credentials.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsGetLocalCredentialsUseCase 8 | implements UseCase, void> { 9 | final StreamelementsRepository streamelementsRepository; 10 | 11 | StreamElementsGetLocalCredentialsUseCase({ 12 | required this.streamelementsRepository, 13 | }); 14 | 15 | @override 16 | Future> call({void params}) { 17 | return streamelementsRepository.getSeCredentialsFromLocal(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/chats/chat_message/kick/kick_emote.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class KickEmote extends StatelessWidget { 5 | final String emoteId; 6 | final double height; 7 | 8 | const KickEmote({ 9 | super.key, 10 | required this.emoteId, 11 | required this.height, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return CachedNetworkImage( 17 | imageUrl: "https://files.kick.com/emotes/$emoteId/fullsize", 18 | width: height, 19 | height: height, 20 | placeholder: (BuildContext context, String url) => 21 | const CircularProgressIndicator(), 22 | errorWidget: (BuildContext context, String url, error) => 23 | const Icon(Icons.error), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/chat_events_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'chat_events_settings_dto.freezed.dart'; 4 | part 'chat_events_settings_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class ChatEventsSettingsDTO with _$ChatEventsSettingsDTO { 8 | factory ChatEventsSettingsDTO({ 9 | @Default(true) bool firstsMessages, 10 | @Default(true) bool subscriptions, 11 | @Default(true) bool bitsDonations, 12 | @Default(true) bool announcements, 13 | @Default(true) bool incomingRaids, 14 | @Default(true) bool redemptions, 15 | }) = _ChatEventsSettingsDTO; 16 | ChatEventsSettingsDTO._(); 17 | 18 | factory ChatEventsSettingsDTO.blank() => ChatEventsSettingsDTO(); 19 | factory ChatEventsSettingsDTO.fromJson(Map json) => 20 | _$ChatEventsSettingsDTOFromJson(json); 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/chats/chat_message/twitch/twitch_emote.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class TwitchEmote extends StatelessWidget { 5 | final MapEntry emote; 6 | final double height; 7 | 8 | const TwitchEmote({ 9 | super.key, 10 | required this.emote, 11 | required this.height, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return CachedNetworkImage( 17 | imageUrl: 18 | "https://static-cdn.jtvnw.net/emoticons/v2/${emote.key}/default/dark/1.0", 19 | height: height, 20 | placeholder: (BuildContext context, String url) => 21 | const CircularProgressIndicator(), 22 | errorWidget: (BuildContext context, String url, error) => 23 | const Icon(Icons.error), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ios/ci_scripts/ci_post_clone.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Fail this script if any subcommand fails. 4 | set -e 5 | 6 | # The default execution directory of this script is the ci_scripts directory. 7 | cd $CI_PRIMARY_REPOSITORY_PATH # change working directory to the root of your cloned repo. 8 | 9 | # Install Flutter using git. 10 | git clone https://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter 11 | export PATH="$PATH:$HOME/flutter/bin" 12 | 13 | # Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms. 14 | flutter precache --ios 15 | 16 | # Install Flutter dependencies. 17 | flutter pub get 18 | 19 | # Install CocoaPods using Homebrew. 20 | HOMEBREW_NO_AUTO_UPDATE=1 # disable homebrew's automatic updates. 21 | brew install cocoapods 22 | 23 | # Install CocoaPods dependencies. 24 | cd ios && pod install # run `pod install` in the `ios` directory. 25 | 26 | exit 0 -------------------------------------------------------------------------------- /lib/src/presentation/widgets/chats/chat_message/shared/third_part_emote.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:irllink/src/domain/entities/chat/chat_emote.dart'; 4 | 5 | class ThirdPartEmote extends StatelessWidget { 6 | final ChatEmote emote; 7 | final double height; 8 | 9 | const ThirdPartEmote({ 10 | super.key, 11 | required this.emote, 12 | required this.height, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return CachedNetworkImage( 18 | imageUrl: emote.url1x, 19 | height: height, 20 | placeholder: (BuildContext context, String url) => 21 | const CircularProgressIndicator(), 22 | errorWidget: (BuildContext context, String url, error) => 23 | const Icon(Icons.error), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/data/entities/kick/kick_user_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'kick_user_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _KickUserDTO _$KickUserDTOFromJson(Map json) => _KickUserDTO( 10 | userId: (json['user_id'] as num).toInt(), 11 | name: json['name'] as String, 12 | email: json['email'] as String, 13 | profilePicture: json['profile_picture'] as String, 14 | ); 15 | 16 | Map _$KickUserDTOToJson(_KickUserDTO instance) => 17 | { 18 | 'user_id': instance.userId, 19 | 'name': instance.name, 20 | 'email': instance.email, 21 | 'profile_picture': instance.profilePicture, 22 | }; 23 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/general_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'general_settings_dto.freezed.dart'; 4 | part 'general_settings_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class GeneralSettingsDTO with _$GeneralSettingsDTO { 8 | const factory GeneralSettingsDTO({ 9 | @Default(true) bool isDarkMode, 10 | @Default(true) bool keepSpeakerOn, 11 | @Default(true) bool displayViewerCount, 12 | @Default({"languageCode": "en", "countryCode": "US"}) 13 | Map appLanguage, 14 | @Default([0.5, 0.5]) List splitViewWeights, 15 | }) = _GeneralSettingsDTO; 16 | const GeneralSettingsDTO._(); 17 | 18 | factory GeneralSettingsDTO.blank() => const GeneralSettingsDTO(); 19 | factory GeneralSettingsDTO.fromJson(Map json) => 20 | _$GeneralSettingsDTOFromJson(json); 21 | } 22 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_decoded_idtoken_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'twitch_decoded_idtoken_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _TwitchDecodedIdTokenDTO _$TwitchDecodedIdTokenDTOFromJson( 10 | Map json) => 11 | _TwitchDecodedIdTokenDTO( 12 | preferredUsername: json['preferredUsername'] as String, 13 | profilePicture: json['profilePicture'] as String, 14 | ); 15 | 16 | Map _$TwitchDecodedIdTokenDTOToJson( 17 | _TwitchDecodedIdTokenDTO instance) => 18 | { 19 | 'preferredUsername': instance.preferredUsername, 20 | 'profilePicture': instance.profilePicture, 21 | }; 22 | -------------------------------------------------------------------------------- /lib/src/data/entities/rtmp_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'rtmp_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _RtmpDTO _$RtmpDTOFromJson(Map json) => _RtmpDTO( 10 | id: (json['id'] as num).toInt(), 11 | name: json['name'] as String, 12 | url: json['url'] as String, 13 | key: json['key'] as String, 14 | createdAt: DateTime.parse(json['createdAt'] as String), 15 | ); 16 | 17 | Map _$RtmpDTOToJson(_RtmpDTO instance) => { 18 | 'id': instance.id, 19 | 'name': instance.name, 20 | 'url': instance.url, 21 | 'key': instance.key, 22 | 'createdAt': instance.createdAt.toIso8601String(), 23 | }; 24 | -------------------------------------------------------------------------------- /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 | 15.6 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/login_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/params/streamelements_auth_params.dart'; 4 | import 'package:irllink/src/core/usecases/usecase.dart'; 5 | import 'package:irllink/src/domain/entities/stream_elements/se_credentials.dart'; 6 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 7 | 8 | class StreamElementsLoginUseCase 9 | implements 10 | UseCase, StreamelementsAuthParams> { 11 | final StreamelementsRepository streamelementsRepository; 12 | 13 | StreamElementsLoginUseCase({required this.streamelementsRepository}); 14 | 15 | @override 16 | Future> call({ 17 | required StreamelementsAuthParams params, 18 | }) { 19 | return streamelementsRepository.login(params); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/assets/icon-github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/src/core/params/twitch_auth_params.dart: -------------------------------------------------------------------------------- 1 | import 'package:irllink/src/core/utils/constants.dart'; 2 | 3 | class TwitchAuthParams { 4 | final String clientId; 5 | final String responseType; 6 | final String scopes; 7 | final String forceVerify; 8 | final String claims; 9 | 10 | const TwitchAuthParams({ 11 | this.clientId = kTwitchAuthClientId, 12 | this.responseType = 'code', 13 | this.scopes = 'openid ' 14 | 'channel_editor channel:moderate ' 15 | 'chat:read chat:edit ' 16 | 'moderator:manage:chat_settings ' 17 | 'moderator:manage:banned_users ' 18 | 'moderator:manage:chat_messages ' 19 | 'channel:manage:broadcast ' 20 | 'channel:read:polls channel:manage:polls ' 21 | 'channel:read:predictions channel:manage:predictions ' 22 | 'channel:read:hype_train', 23 | this.forceVerify = 'true', 24 | this.claims = '{"userinfo":{"picture":null, "preferred_username":null}}', 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/data/entities/stream_elements/se_credentials_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'se_credentials_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _SeCredentialsDTO _$SeCredentialsDTOFromJson(Map json) => 10 | _SeCredentialsDTO( 11 | accessToken: json['access_token'] as String, 12 | refreshToken: json['refresh_token'] as String, 13 | expiresIn: (json['expires_in'] as num).toInt(), 14 | scopes: json['scopes'] as String, 15 | ); 16 | 17 | Map _$SeCredentialsDTOToJson(_SeCredentialsDTO instance) => 18 | { 19 | 'access_token': instance.accessToken, 20 | 'refresh_token': instance.refreshToken, 21 | 'expires_in': instance.expiresIn, 22 | 'scopes': instance.scopes, 23 | }; 24 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_me_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_me.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsGetMeParams { 8 | final String token; 9 | 10 | StreamElementsGetMeParams({ 11 | required this.token, 12 | }); 13 | } 14 | 15 | class StreamElementsGetMeUseCase 16 | implements UseCase, StreamElementsGetMeParams> { 17 | final StreamelementsRepository streamelementsRepository; 18 | 19 | StreamElementsGetMeUseCase({required this.streamelementsRepository}); 20 | 21 | @override 22 | Future> call({ 23 | required StreamElementsGetMeParams params, 24 | }) { 25 | return streamelementsRepository.getMe(params.token); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/hidden_user_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'hidden_user_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _HiddenUserDTO _$HiddenUserDTOFromJson(Map json) => 10 | _HiddenUserDTO( 11 | id: json['id'] as String, 12 | username: json['username'] as String, 13 | platform: $enumDecode(_$PlatformEnumMap, json['platform']), 14 | ); 15 | 16 | Map _$HiddenUserDTOToJson(_HiddenUserDTO instance) => 17 | { 18 | 'id': instance.id, 19 | 'username': instance.username, 20 | 'platform': _$PlatformEnumMap[instance.platform]!, 21 | }; 22 | 23 | const _$PlatformEnumMap = { 24 | Platform.twitch: 'twitch', 25 | Platform.kick: 'kick', 26 | Platform.youtube: 'youtube', 27 | }; 28 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/premium_feature_badge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | 4 | Widget premiumFeatureBadge( 5 | BuildContext context, 6 | ) { 7 | return Container( 8 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), 9 | margin: const EdgeInsets.only(left: 4), 10 | decoration: BoxDecoration( 11 | color: Theme.of(context).colorScheme.tertiary, 12 | borderRadius: BorderRadius.circular(20), 13 | ), 14 | child: Wrap( 15 | crossAxisAlignment: WrapCrossAlignment.center, 16 | children: [ 17 | Text( 18 | "premium_feature".tr, 19 | style: const TextStyle( 20 | fontSize: 12, 21 | ), 22 | ), 23 | const SizedBox( 24 | width: 4, 25 | ), 26 | const Icon( 27 | Icons.star, 28 | size: 12, 29 | color: Colors.yellow, 30 | ), 31 | ], 32 | ), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/unban_kick_user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 5 | 6 | class UnbanKickUserParams { 7 | final String accessToken; 8 | final String broadcasterUserId; 9 | final String userToUnbanId; 10 | 11 | const UnbanKickUserParams({ 12 | required this.accessToken, 13 | required this.broadcasterUserId, 14 | required this.userToUnbanId, 15 | }); 16 | } 17 | 18 | class UnbanKickUserUseCase 19 | implements UseCase, UnbanKickUserParams> { 20 | final KickRepository repository; 21 | 22 | UnbanKickUserUseCase(this.repository); 23 | 24 | @override 25 | Future> call({ 26 | required UnbanKickUserParams params, 27 | }) { 28 | return repository.unbanUser( 29 | params: params, 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/next_song_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 5 | 6 | class StreamElementsNextSongParams { 7 | final String token; 8 | final String channel; 9 | 10 | StreamElementsNextSongParams({ 11 | required this.token, 12 | required this.channel, 13 | }); 14 | } 15 | 16 | class StreamElementsNextSongUseCase 17 | implements UseCase, StreamElementsNextSongParams> { 18 | final StreamelementsRepository streamelementsRepository; 19 | 20 | StreamElementsNextSongUseCase({required this.streamelementsRepository}); 21 | 22 | @override 23 | Future> call({ 24 | required StreamElementsNextSongParams params, 25 | }) { 26 | return streamelementsRepository.nextSong(params.token, params.channel); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/get_recent_messages.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 5 | 6 | class GetRecentMessagesUseCaseParams { 7 | final String channelName; 8 | final int limit; 9 | 10 | GetRecentMessagesUseCaseParams({ 11 | required this.channelName, 12 | required this.limit, 13 | }); 14 | } 15 | 16 | class GetRecentMessagesUseCase 17 | implements UseCase, GetRecentMessagesUseCaseParams> { 18 | final TwitchRepository twitchRepository; 19 | 20 | GetRecentMessagesUseCase(this.twitchRepository); 21 | 22 | @override 23 | Future>> call({ 24 | required GetRecentMessagesUseCaseParams params, 25 | }) { 26 | return twitchRepository.getRecentMessages( 27 | params.channelName, 28 | params.limit, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_hype_train_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'twitch_hype_train_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _ContributionDTO _$ContributionDTOFromJson(Map json) => 10 | _ContributionDTO( 11 | userId: json['user_id'] as String, 12 | userLogin: json['user_login'] as String, 13 | userName: json['user_name'] as String, 14 | type: json['type'] as String, 15 | total: (json['total'] as num).toInt(), 16 | ); 17 | 18 | Map _$ContributionDTOToJson(_ContributionDTO instance) => 19 | { 20 | 'user_id': instance.userId, 21 | 'user_login': instance.userLogin, 22 | 'user_name': instance.userName, 23 | 'type': instance.type, 24 | 'total': instance.total, 25 | }; 26 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/post_kick_chat_nessage_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 5 | 6 | class PostKickChatMessageParams { 7 | final String accessToken; 8 | final String message; 9 | final int broadcasterUserId; 10 | const PostKickChatMessageParams({ 11 | required this.accessToken, 12 | required this.message, 13 | required this.broadcasterUserId, 14 | }); 15 | } 16 | 17 | class PostKickChatMessageUseCase 18 | implements UseCase, PostKickChatMessageParams> { 19 | final KickRepository repository; 20 | 21 | PostKickChatMessageUseCase(this.repository); 22 | 23 | @override 24 | Future> call({ 25 | required PostKickChatMessageParams params, 26 | }) { 27 | return repository.sendChatMessage( 28 | params, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/get_twitch_user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_user.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class GetTwitchUserUseCaseParams { 8 | final String? username; 9 | final String accessToken; 10 | 11 | GetTwitchUserUseCaseParams({this.username, required this.accessToken}); 12 | } 13 | 14 | class GetTwitchUserUseCase 15 | implements 16 | UseCase, GetTwitchUserUseCaseParams> { 17 | final TwitchRepository twitchRepository; 18 | 19 | GetTwitchUserUseCase(this.twitchRepository); 20 | 21 | @override 22 | Future> call({ 23 | required GetTwitchUserUseCaseParams params, 24 | }) { 25 | return twitchRepository.getTwitchUser(params.username, params.accessToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/reset_queue_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 5 | 6 | class StreamElementsResetQueueParams { 7 | final String token; 8 | final String channel; 9 | 10 | StreamElementsResetQueueParams({ 11 | required this.token, 12 | required this.channel, 13 | }); 14 | } 15 | 16 | class StreamElementsResetQueueUseCase 17 | implements UseCase, StreamElementsResetQueueParams> { 18 | final StreamelementsRepository streamelementsRepository; 19 | 20 | StreamElementsResetQueueUseCase({required this.streamelementsRepository}); 21 | 22 | @override 23 | Future> call({ 24 | required StreamElementsResetQueueParams params, 25 | }) { 26 | return streamelementsRepository.resetQueue(params.token, params.channel); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | maven { url=uri("https://jitpack.io") } 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "8.7.2" apply false 23 | id "org.jetbrains.kotlin.android" version "2.1.0" apply false 24 | id "com.google.gms.google-services" version "4.4.2" apply false 25 | id "com.google.firebase.crashlytics" version "2.8.1" apply false 26 | } 27 | 28 | include ":app" -------------------------------------------------------------------------------- /lib/assets/discord-mark-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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. 5 | 6 | version: 7 | revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 17 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 18 | - platform: ios 19 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 20 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_prediction_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'twitch_prediction_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _OutcomeDTO _$OutcomeDTOFromJson(Map json) => _OutcomeDTO( 10 | id: json['id'] as String, 11 | title: json['title'] as String, 12 | users: (json['users'] as num?)?.toInt() ?? 0, 13 | channelPoints: (json['channel_points'] as num?)?.toInt() ?? 0, 14 | color: const ColorConverter().fromJson(json['color'] as String), 15 | ); 16 | 17 | Map _$OutcomeDTOToJson(_OutcomeDTO instance) => 18 | { 19 | 'id': instance.id, 20 | 'title': instance.title, 21 | 'users': instance.users, 22 | 'channel_points': instance.channelPoints, 23 | 'color': const ColorConverter().toJson(instance.color), 24 | }; 25 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/alert_message.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Widget alertMessage({ 4 | required Color color, 5 | required String message, 6 | required bool isProgress, 7 | }) { 8 | return AnimatedContainer( 9 | padding: const EdgeInsets.only(bottom: 5, top: 5), 10 | color: color, 11 | duration: const Duration(milliseconds: 400), 12 | child: Row( 13 | mainAxisAlignment: MainAxisAlignment.center, 14 | children: [ 15 | Text( 16 | message, 17 | style: const TextStyle( 18 | color: Colors.white, 19 | fontSize: 16, 20 | fontWeight: FontWeight.bold, 21 | ), 22 | ), 23 | Visibility( 24 | visible: isProgress, 25 | child: Container( 26 | margin: const EdgeInsets.only(left: 8), 27 | width: 20, 28 | height: 20, 29 | child: const CircularProgressIndicator(color: Colors.white), 30 | ), 31 | ), 32 | ], 33 | ), 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/ban_kick_user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 5 | 6 | class BanKickUserParams { 7 | final String accessToken; 8 | final int broadcasterUserId; 9 | final int userToBanId; 10 | final String? reason; 11 | final int? duration; 12 | 13 | const BanKickUserParams({ 14 | required this.accessToken, 15 | required this.broadcasterUserId, 16 | required this.userToBanId, 17 | this.reason, 18 | this.duration, 19 | }); 20 | } 21 | 22 | class BanKickUserUseCase 23 | implements UseCase, BanKickUserParams> { 24 | final KickRepository repository; 25 | 26 | BanKickUserUseCase(this.repository); 27 | 28 | @override 29 | Future> call({ 30 | required BanKickUserParams params, 31 | }) { 32 | return repository.banUser( 33 | params: params, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/get_twitch_users_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_user.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class GetTwitchUsersUseCaseParams { 8 | final List ids; 9 | final String accessToken; 10 | 11 | GetTwitchUsersUseCaseParams({ 12 | required this.ids, 13 | required this.accessToken, 14 | }); 15 | } 16 | 17 | class GetTwitchUsersUseCase 18 | implements 19 | UseCase>, 20 | GetTwitchUsersUseCaseParams> { 21 | final TwitchRepository twitchRepository; 22 | 23 | GetTwitchUsersUseCase(this.twitchRepository); 24 | 25 | @override 26 | Future>> call({ 27 | required GetTwitchUsersUseCaseParams params, 28 | }) { 29 | return twitchRepository.getTwitchUsers(params.ids, params.accessToken); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/get_kick_categories_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/kick/kick_category.dart'; 5 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 6 | 7 | class KickCategoriesParams { 8 | final String accessToken; 9 | final String searchQuery; 10 | final int? page; 11 | 12 | const KickCategoriesParams({ 13 | required this.accessToken, 14 | required this.searchQuery, 15 | this.page, 16 | }); 17 | } 18 | 19 | class GetKickCategoriesUseCase 20 | implements 21 | UseCase>, KickCategoriesParams> { 22 | final KickRepository repository; 23 | 24 | GetKickCategoriesUseCase(this.repository); 25 | 26 | @override 27 | Future>> call({ 28 | required KickCategoriesParams params, 29 | }) { 30 | return repository.getCategories( 31 | params: params, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/kick/patch_kick_channel_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/kick/kick_channel.dart'; 5 | import 'package:irllink/src/domain/repositories/kick_repository.dart'; 6 | 7 | class PatchKickChannelParams { 8 | final String accessToken; 9 | final String streamTitle; 10 | final String categoryId; 11 | 12 | PatchKickChannelParams({ 13 | required this.accessToken, 14 | required this.streamTitle, 15 | required this.categoryId, 16 | }); 17 | } 18 | 19 | class PatchKickChannelUseCase 20 | implements UseCase, PatchKickChannelParams> { 21 | final KickRepository kickRepository; 22 | 23 | PatchKickChannelUseCase(this.kickRepository); 24 | 25 | @override 26 | Future> call({ 27 | required PatchKickChannelParams params, 28 | }) { 29 | return kickRepository.patchChannel( 30 | params, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/set_stream_title_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 5 | 6 | class SetStreamTitleUseCaseParams { 7 | final String accessToken; 8 | final String broadcasterId; 9 | final String title; 10 | 11 | SetStreamTitleUseCaseParams({ 12 | required this.accessToken, 13 | required this.broadcasterId, 14 | required this.title, 15 | }); 16 | } 17 | 18 | class SetStreamTitleUseCase 19 | implements UseCase, SetStreamTitleUseCaseParams> { 20 | final TwitchRepository twitchRepository; 21 | 22 | SetStreamTitleUseCase(this.twitchRepository); 23 | 24 | @override 25 | Future> call({ 26 | required SetStreamTitleUseCaseParams params, 27 | }) { 28 | return twitchRepository.setStreamTitle( 29 | params.accessToken, 30 | params.broadcasterId, 31 | params.title, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/src/data/entities/kick/kick_credentials_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'kick_credentials_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _KickCredentialsDTO _$KickCredentialsDTOFromJson(Map json) => 10 | _KickCredentialsDTO( 11 | accessToken: json['accessToken'] as String, 12 | refreshToken: json['refreshToken'] as String, 13 | expiresIn: (json['expiresIn'] as num).toInt(), 14 | kickUser: KickUserDTO.fromJson(json['kickUser'] as Map), 15 | scopes: json['scopes'] as String, 16 | ); 17 | 18 | Map _$KickCredentialsDTOToJson(_KickCredentialsDTO instance) => 19 | { 20 | 'accessToken': instance.accessToken, 21 | 'refreshToken': instance.refreshToken, 22 | 'expiresIn': instance.expiresIn, 23 | 'kickUser': instance.kickUser, 24 | 'scopes': instance.scopes, 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 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | 48 | /android/app/google-services.json 49 | /ios/Runner/GoogleService-Info.plist 50 | firebase.json 51 | 52 | # Patrol related 53 | /integration_test/test_bundle.dart 54 | -------------------------------------------------------------------------------- /lib/src/domain/entities/twitch/twitch_poll.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum PollStatus { 4 | empty, 5 | active, 6 | completed, 7 | } 8 | 9 | @immutable 10 | class TwitchPoll { 11 | final String id; 12 | final String title; 13 | final List choices; 14 | final int totalVotes; 15 | final PollStatus status; 16 | final DateTime endsAt; 17 | 18 | const TwitchPoll({ 19 | required this.id, 20 | required this.title, 21 | required this.choices, 22 | required this.totalVotes, 23 | required this.status, 24 | required this.endsAt, 25 | }); 26 | 27 | factory TwitchPoll.empty() { 28 | return TwitchPoll( 29 | id: '', 30 | title: '', 31 | choices: const [], 32 | totalVotes: 0, 33 | status: PollStatus.empty, 34 | endsAt: DateTime.now(), 35 | ); 36 | } 37 | } 38 | 39 | @immutable 40 | class Choice { 41 | final String id; 42 | final String title; 43 | final int votes; 44 | 45 | const Choice({ 46 | required this.id, 47 | required this.title, 48 | required this.votes, 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/stream_elements_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'stream_elements_settings_dto.freezed.dart'; 4 | part 'stream_elements_settings_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class StreamElementsSettingsDTO with _$StreamElementsSettingsDTO { 8 | const factory StreamElementsSettingsDTO({ 9 | @Default(true) bool showFollowerActivity, 10 | @Default(true) bool showSubscriberActivity, 11 | @Default(true) bool showDonationActivity, 12 | @Default(true) bool showCheerActivity, 13 | @Default(true) bool showRaidActivity, 14 | @Default(true) bool showHostActivity, 15 | @Default(true) bool showMerchActivity, 16 | required String? jwt, 17 | required String? overlayToken, 18 | @Default([]) List mutedOverlays, 19 | }) = _StreamElementsSettingsDTO; 20 | 21 | factory StreamElementsSettingsDTO.blank() => 22 | const StreamElementsSettingsDTO(jwt: null, overlayToken: null); 23 | factory StreamElementsSettingsDTO.fromJson(Map json) => 24 | _$StreamElementsSettingsDTOFromJson(json); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/tts_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'tts_settings_dto.freezed.dart'; 4 | part 'tts_settings_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class TtsSettingsDTO with _$TtsSettingsDTO { 8 | factory TtsSettingsDTO({ 9 | @Default(false) bool ttsEnabled, 10 | @Default("en-US") String language, 11 | @Default([]) List prefixsToIgnore, 12 | @Default([]) List prefixsToUseTtsOnly, 13 | @Default(1) double volume, 14 | @Default(1) double pitch, 15 | @Default(0.5) double rate, 16 | @Default({"name": "en-us-x-sfg-local", "locale": "en-US"}) 17 | Map voice, 18 | @Default([]) List ttsUsersToIgnore, 19 | @Default(false) bool ttsMuteViewerName, 20 | @Default(false) bool ttsOnlyVip, 21 | @Default(false) bool ttsOnlyMod, 22 | @Default(false) bool ttsOnlySubscriber, 23 | }) = _TtsSettingsDTO; 24 | TtsSettingsDTO._(); 25 | 26 | factory TtsSettingsDTO.blank() => TtsSettingsDTO(); 27 | factory TtsSettingsDTO.fromJson(Map json) => 28 | _$TtsSettingsDTOFromJson(json); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/remove_song_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 5 | 6 | class StreamElementsRemoveSongParams { 7 | final String token; 8 | final String channel; 9 | final String songId; 10 | 11 | StreamElementsRemoveSongParams({ 12 | required this.token, 13 | required this.channel, 14 | required this.songId, 15 | }); 16 | } 17 | 18 | class StreamElementsRemoveSongUseCase 19 | implements UseCase, StreamElementsRemoveSongParams> { 20 | final StreamelementsRepository streamelementsRepository; 21 | 22 | StreamElementsRemoveSongUseCase({required this.streamelementsRepository}); 23 | 24 | @override 25 | Future> call({ 26 | required StreamElementsRemoveSongParams params, 27 | }) { 28 | return streamelementsRepository.removeSong( 29 | params.token, 30 | params.channel, 31 | params.songId, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/get_stream_info_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_stream_infos.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class GetStreamInfoUseCaseParams { 8 | final String accessToken; 9 | final String broadcasterId; 10 | 11 | GetStreamInfoUseCaseParams({ 12 | required this.accessToken, 13 | required this.broadcasterId, 14 | }); 15 | } 16 | 17 | class GetStreamInfoUseCase 18 | implements 19 | UseCase, 20 | GetStreamInfoUseCaseParams> { 21 | final TwitchRepository twitchRepository; 22 | 23 | GetStreamInfoUseCase(this.twitchRepository); 24 | 25 | @override 26 | Future> call({ 27 | required GetStreamInfoUseCaseParams params, 28 | }) { 29 | return twitchRepository.getStreamInfo( 30 | params.accessToken, 31 | params.broadcasterId, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/delete_message_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 5 | import 'package:twitch_chat/twitch_chat.dart'; 6 | 7 | class DeleteMessageUseCaseParams { 8 | final String accessToken; 9 | final String broadcasterId; 10 | final ChatMessage message; 11 | 12 | DeleteMessageUseCaseParams({ 13 | required this.accessToken, 14 | required this.broadcasterId, 15 | required this.message, 16 | }); 17 | } 18 | 19 | class DeleteMessageUseCase 20 | implements UseCase, DeleteMessageUseCaseParams> { 21 | final TwitchRepository twitchRepository; 22 | 23 | DeleteMessageUseCase(this.twitchRepository); 24 | 25 | @override 26 | Future> call({ 27 | required DeleteMessageUseCaseParams params, 28 | }) { 29 | return twitchRepository.deleteMessage( 30 | params.accessToken, 31 | params.broadcasterId, 32 | params.message, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/domain/entities/settings/chat_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:irllink/src/domain/entities/chat/chat_message.dart'; 3 | 4 | @immutable 5 | class ChatSettings { 6 | final bool hideDeletedMessages; 7 | 8 | const ChatSettings({ 9 | required this.hideDeletedMessages, 10 | }); 11 | 12 | ChatSettings copyWith({ 13 | bool? hideDeletedMessages, 14 | }) { 15 | return ChatSettings( 16 | hideDeletedMessages: hideDeletedMessages ?? this.hideDeletedMessages, 17 | ); 18 | } 19 | } 20 | 21 | class ChatGroup { 22 | final String id; 23 | final List channels; 24 | 25 | const ChatGroup({ 26 | required this.id, 27 | required this.channels, 28 | }); 29 | 30 | ChatGroup copyWith({ 31 | String? id, 32 | List? channels, 33 | }) { 34 | return ChatGroup( 35 | id: id ?? this.id, 36 | channels: channels ?? this.channels, 37 | ); 38 | } 39 | } 40 | 41 | class Channel { 42 | final Platform platform; 43 | final String channel; 44 | 45 | const Channel({ 46 | required this.platform, 47 | required this.channel, 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/domain/entities/settings/general_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class GeneralSettings { 5 | final bool isDarkMode; 6 | final bool keepSpeakerOn; 7 | final bool displayViewerCount; 8 | final Map appLanguage; 9 | final List splitViewWeights; 10 | 11 | const GeneralSettings({ 12 | required this.isDarkMode, 13 | required this.keepSpeakerOn, 14 | required this.displayViewerCount, 15 | required this.appLanguage, 16 | required this.splitViewWeights, 17 | }); 18 | 19 | GeneralSettings copyWith({ 20 | bool? isDarkMode, 21 | bool? keepSpeakerOn, 22 | bool? displayViewerCount, 23 | Map? appLanguage, 24 | List? splitViewWeights, 25 | }) { 26 | return GeneralSettings( 27 | isDarkMode: isDarkMode ?? this.isDarkMode, 28 | keepSpeakerOn: keepSpeakerOn ?? this.keepSpeakerOn, 29 | displayViewerCount: displayViewerCount ?? this.displayViewerCount, 30 | appLanguage: appLanguage ?? this.appLanguage, 31 | splitViewWeights: splitViewWeights ?? this.splitViewWeights, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_overlays_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_overlay.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsGetOverlaysParams { 8 | final String token; 9 | final String channel; 10 | 11 | StreamElementsGetOverlaysParams({ 12 | required this.token, 13 | required this.channel, 14 | }); 15 | } 16 | 17 | class StreamElementsGetOverlaysUseCase 18 | implements 19 | UseCase>, 20 | StreamElementsGetOverlaysParams> { 21 | final StreamelementsRepository streamelementsRepository; 22 | 23 | StreamElementsGetOverlaysUseCase({required this.streamelementsRepository}); 24 | 25 | @override 26 | Future>> call({ 27 | required StreamElementsGetOverlaysParams params, 28 | }) { 29 | return streamelementsRepository.getOverlays(params.token, params.channel); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_song_queue_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_song.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsGetSongQueueParams { 8 | final String token; 9 | final String channel; 10 | 11 | StreamElementsGetSongQueueParams({ 12 | required this.token, 13 | required this.channel, 14 | }); 15 | } 16 | 17 | class StreamElementsGetSongQueueUseCase 18 | implements 19 | UseCase>, 20 | StreamElementsGetSongQueueParams> { 21 | final StreamelementsRepository streamelementsRepository; 22 | 23 | StreamElementsGetSongQueueUseCase({required this.streamelementsRepository}); 24 | 25 | @override 26 | Future>> call({ 27 | required StreamElementsGetSongQueueParams params, 28 | }) { 29 | return streamelementsRepository.getSongQueue(params.token, params.channel); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/create_poll_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_poll.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class CreatePollUseCaseParams { 8 | final String accessToken; 9 | final String broadcasterId; 10 | final TwitchPoll newPoll; 11 | 12 | CreatePollUseCaseParams({ 13 | required this.accessToken, 14 | required this.broadcasterId, 15 | required this.newPoll, 16 | }); 17 | } 18 | 19 | class CreatePollUseCase 20 | implements UseCase, CreatePollUseCaseParams> { 21 | final TwitchRepository twitchRepository; 22 | 23 | CreatePollUseCase({required this.twitchRepository}); 24 | 25 | @override 26 | Future> call({ 27 | required CreatePollUseCaseParams params, 28 | }) { 29 | return twitchRepository.createPoll( 30 | params.accessToken, 31 | params.broadcasterId, 32 | params.newPoll, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | # flutter pub run flutter_launcher_icons 2 | flutter_launcher_icons: 3 | image_path: "lib/assets/icon/icon.png" 4 | 5 | android: "launcher_icon" 6 | # image_path_android: "assets/icon/icon.png" 7 | min_sdk_android: 21 # android min sdk min:16, default 21 8 | adaptive_icon_background: "lib/assets/icon/background.png" 9 | adaptive_icon_foreground: "lib/assets/icon/foreground.png" 10 | # adaptive_icon_monochrome: "assets/icon/monochrome.png" 11 | 12 | ios: true 13 | # image_path_ios: "assets/icon/icon.png" 14 | remove_alpha_ios: true 15 | remove_alpha_channel_ios: true 16 | # image_path_ios_dark_transparent: "assets/icon/icon_dark.png" 17 | # image_path_ios_tinted_grayscale: "assets/icon/icon_tinted.png" 18 | # desaturate_tinted_to_grayscale_ios: true 19 | 20 | web: 21 | generate: false 22 | image_path: "path/to/image.png" 23 | background_color: "#hexcode" 24 | theme_color: "#hexcode" 25 | 26 | windows: 27 | generate: false 28 | image_path: "path/to/image.png" 29 | icon_size: 48 # min:48, max:256, default: 48 30 | 31 | macos: 32 | generate: false 33 | image_path: "path/to/image.png" 34 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/browser_tab_settings_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'browser_tab_settings_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _BrowserTabDTO _$BrowserTabDTOFromJson(Map json) => 10 | _BrowserTabDTO( 11 | id: json['id'] as String, 12 | title: json['title'] as String, 13 | url: json['url'] as String, 14 | toggled: const BoolToIntConverter().fromJson(json['toggled'] as Object), 15 | iOSAudioSource: const BoolToIntConverter() 16 | .fromJson(json['is_ios_audio_source'] as Object), 17 | ); 18 | 19 | Map _$BrowserTabDTOToJson(_BrowserTabDTO instance) => 20 | { 21 | 'id': instance.id, 22 | 'title': instance.title, 23 | 'url': instance.url, 24 | 'toggled': const BoolToIntConverter().toJson(instance.toggled), 25 | 'is_ios_audio_source': 26 | const BoolToIntConverter().toJson(instance.iOSAudioSource), 27 | }; 28 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_song_playing_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_song.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsGetSongPlayingParams { 8 | final String token; 9 | final String channel; 10 | 11 | StreamElementsGetSongPlayingParams({ 12 | required this.token, 13 | required this.channel, 14 | }); 15 | } 16 | 17 | class StreamElementsGetSongPlayingUseCase 18 | implements 19 | UseCase, StreamElementsGetSongPlayingParams> { 20 | final StreamelementsRepository streamelementsRepository; 21 | 22 | StreamElementsGetSongPlayingUseCase({required this.streamelementsRepository}); 23 | 24 | @override 25 | Future> call({ 26 | required StreamElementsGetSongPlayingParams params, 27 | }) { 28 | return streamelementsRepository.getSongPlaying( 29 | params.token, 30 | params.channel, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/ban_user_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 5 | import 'package:twitch_chat/twitch_chat.dart'; 6 | 7 | class BanUserUseCaseParams { 8 | final String accessToken; 9 | final String broadcasterId; 10 | final ChatMessage message; 11 | final int? duration; 12 | 13 | BanUserUseCaseParams({ 14 | required this.accessToken, 15 | required this.broadcasterId, 16 | required this.message, 17 | required this.duration, 18 | }); 19 | } 20 | 21 | class BanUserUseCase 22 | implements UseCase, BanUserUseCaseParams> { 23 | final TwitchRepository twitchRepository; 24 | 25 | BanUserUseCase(this.twitchRepository); 26 | 27 | @override 28 | Future> call({ 29 | required BanUserUseCaseParams params, 30 | }) { 31 | return twitchRepository.banUser( 32 | params.accessToken, 33 | params.broadcasterId, 34 | params.message, 35 | params.duration, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/replay_activity_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_activity.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsReplayActivityParams { 8 | final String token; 9 | final SeActivity activity; 10 | 11 | StreamElementsReplayActivityParams({ 12 | required this.token, 13 | required this.activity, 14 | }); 15 | } 16 | 17 | class StreamElementsReplayActivityUseCase 18 | implements 19 | UseCase, StreamElementsReplayActivityParams> { 20 | final StreamelementsRepository streamelementsRepository; 21 | 22 | StreamElementsReplayActivityUseCase({required this.streamelementsRepository}); 23 | 24 | @override 25 | Future> call({ 26 | required StreamElementsReplayActivityParams params, 27 | }) { 28 | return streamelementsRepository.replayActivity( 29 | params.token, 30 | params.activity, 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/chats/chat_message/twitch/cheer_emote.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:irllink/src/domain/entities/chat/chat_emote.dart'; 4 | 5 | class CheerEmote extends StatelessWidget { 6 | final ChatEmote cheerEmote; 7 | final double textSize; 8 | 9 | const CheerEmote({ 10 | super.key, 11 | required this.cheerEmote, 12 | required this.textSize, 13 | }); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Wrap( 18 | children: [ 19 | CachedNetworkImage( 20 | imageUrl: cheerEmote.url1x, 21 | placeholder: (BuildContext context, String url) => 22 | const CircularProgressIndicator(), 23 | errorWidget: (BuildContext context, String url, error) => 24 | const Icon(Icons.error), 25 | ), 26 | Text( 27 | '${cheerEmote.id} ', 28 | style: TextStyle( 29 | color: Color(int.parse(cheerEmote.color!.replaceAll('#', '0xff'))), 30 | fontSize: textSize, 31 | ), 32 | ), 33 | ], 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/end_poll_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_poll.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class EndPollUseCaseParams { 8 | final String accessToken; 9 | final String broadcasterId; 10 | final String pollId; 11 | final String status; 12 | 13 | EndPollUseCaseParams({ 14 | required this.accessToken, 15 | required this.broadcasterId, 16 | required this.pollId, 17 | required this.status, 18 | }); 19 | } 20 | 21 | class EndPollUseCase 22 | implements UseCase, EndPollUseCaseParams> { 23 | final TwitchRepository twitchRepository; 24 | 25 | EndPollUseCase({required this.twitchRepository}); 26 | 27 | @override 28 | Future> call({ 29 | required EndPollUseCaseParams params, 30 | }) { 31 | return twitchRepository.endPoll( 32 | params.accessToken, 33 | params.broadcasterId, 34 | params.pollId, 35 | params.status, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/presentation/controllers/settings/hidden_users_settings_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:irllink/src/domain/entities/settings/hidden_user.dart'; 3 | import 'package:irllink/src/domain/usecases/settings/get_hidden_users_usecase.dart'; 4 | import 'package:irllink/src/domain/usecases/settings/remove_hidden_user_usecase.dart'; 5 | 6 | class HiddenUsersSettingsController extends GetxController { 7 | final GetHiddenUsersUseCase getHiddenUsersUseCase; 8 | final RemoveHiddenUserUseCase removeHiddenUserUseCase; 9 | 10 | HiddenUsersSettingsController({ 11 | required this.getHiddenUsersUseCase, 12 | required this.removeHiddenUserUseCase, 13 | }); 14 | 15 | RxList hiddenUsers = [].obs; 16 | 17 | @override 18 | void onInit() { 19 | super.onInit(); 20 | getHiddenUsers(); 21 | } 22 | 23 | Future getHiddenUsers() async { 24 | final result = await getHiddenUsersUseCase(); 25 | result.fold( 26 | (l) => l.message, 27 | (r) => hiddenUsers.value = r, 28 | ); 29 | } 30 | 31 | void removeHiddenUser(HiddenUser user) { 32 | removeHiddenUserUseCase(params: user); 33 | getHiddenUsers(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/assets/kick/badges/og.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/update_player_state_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 5 | 6 | class StreamElementsUpdatePlayerStateParams { 7 | final String token; 8 | final String channel; 9 | final String state; 10 | 11 | StreamElementsUpdatePlayerStateParams({ 12 | required this.token, 13 | required this.channel, 14 | required this.state, 15 | }); 16 | } 17 | 18 | class StreamElementsUpdatePlayerStateUseCase 19 | implements 20 | UseCase, StreamElementsUpdatePlayerStateParams> { 21 | final StreamelementsRepository streamelementsRepository; 22 | 23 | StreamElementsUpdatePlayerStateUseCase({ 24 | required this.streamelementsRepository, 25 | }); 26 | 27 | @override 28 | Future> call({ 29 | required StreamElementsUpdatePlayerStateParams params, 30 | }) { 31 | return streamelementsRepository.updatePlayerState( 32 | params.token, 33 | params.channel, 34 | params.state, 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/domain/entities/settings/browser_tab_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class BrowserTabSettings { 5 | final List tabs; 6 | 7 | const BrowserTabSettings({ 8 | required this.tabs, 9 | }); 10 | 11 | BrowserTabSettings copyWith({ 12 | List? tabs, 13 | }) { 14 | return BrowserTabSettings( 15 | tabs: tabs ?? this.tabs, 16 | ); 17 | } 18 | } 19 | 20 | class BrowserTab { 21 | final String id; 22 | final String title; 23 | final String url; 24 | final bool toggled; 25 | final bool iOSAudioSource; 26 | 27 | const BrowserTab({ 28 | required this.id, 29 | required this.title, 30 | required this.url, 31 | required this.toggled, 32 | required this.iOSAudioSource, 33 | }); 34 | 35 | BrowserTab copyWith({ 36 | String? id, 37 | String? title, 38 | String? url, 39 | bool? toggled, 40 | bool? iOSAudioSource, 41 | }) { 42 | return BrowserTab( 43 | id: id ?? this.id, 44 | title: title ?? this.title, 45 | url: url ?? this.url, 46 | toggled: toggled ?? this.toggled, 47 | iOSAudioSource: iOSAudioSource ?? this.iOSAudioSource, 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/set_chat_settings_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_stream_infos.dart'; 5 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 6 | 7 | class SetChatSettingsUseCaseParams { 8 | final String accessToken; 9 | final String broadcasterId; 10 | final TwitchStreamInfos? twitchStreamInfos; 11 | 12 | SetChatSettingsUseCaseParams({ 13 | required this.accessToken, 14 | required this.broadcasterId, 15 | required this.twitchStreamInfos, 16 | }); 17 | } 18 | 19 | class SetChatSettingsUseCase 20 | implements UseCase, SetChatSettingsUseCaseParams> { 21 | final TwitchRepository twitchRepository; 22 | 23 | SetChatSettingsUseCase(this.twitchRepository); 24 | 25 | @override 26 | Future> call({ 27 | required SetChatSettingsUseCaseParams params, 28 | }) { 29 | return twitchRepository.setChatSettings( 30 | params.accessToken, 31 | params.broadcasterId, 32 | params.twitchStreamInfos, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/core/params/kick_auth_params.dart: -------------------------------------------------------------------------------- 1 | import 'package:irllink/src/core/utils/constants.dart'; 2 | import 'package:irllink/src/core/utils/pkce_utils.dart'; 3 | import 'package:uuid/uuid.dart'; 4 | 5 | class KickAuthParams { 6 | final String clientId; 7 | final String responseType; 8 | final String scopes; 9 | final String codeChallenge; 10 | final String codeChallengeMethod; 11 | final String state; 12 | final String codeVerifier; 13 | 14 | const KickAuthParams({ 15 | this.clientId = kKickAuthClientId, 16 | this.responseType = "code", 17 | this.scopes = 18 | "user:read channel:read channel:write chat:write streamkey:read events:subscribe moderation:ban", 19 | this.codeChallenge = "", 20 | this.codeChallengeMethod = "S256", 21 | this.state = "", 22 | this.codeVerifier = "", 23 | }); 24 | 25 | factory KickAuthParams.withPKCE() { 26 | final codeVerifier = PKCEUtils.generateCodeVerifier(); 27 | final codeChallenge = PKCEUtils.generateCodeChallenge(codeVerifier); 28 | final state = const Uuid().v4(); 29 | 30 | return KickAuthParams( 31 | codeChallenge: codeChallenge, 32 | state: state, 33 | codeVerifier: codeVerifier, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_user_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'twitch_user_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _TwitchUserDTO _$TwitchUserDTOFromJson(Map json) => 10 | _TwitchUserDTO( 11 | id: json['id'] as String, 12 | login: json['login'] as String, 13 | displayName: json['display_name'] as String, 14 | broadcasterType: json['broadcaster_type'] as String, 15 | description: json['description'] as String, 16 | profileImageUrl: json['profile_image_url'] as String, 17 | viewCount: _stringToInt(json['view_count']), 18 | ); 19 | 20 | Map _$TwitchUserDTOToJson(_TwitchUserDTO instance) => 21 | { 22 | 'id': instance.id, 23 | 'login': instance.login, 24 | 'display_name': instance.displayName, 25 | 'broadcaster_type': instance.broadcasterType, 26 | 'description': instance.description, 27 | 'profile_image_url': instance.profileImageUrl, 28 | 'view_count': instance.viewCount, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/src/domain/entities/settings/chat_events_settings.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class ChatEventsSettings { 5 | final bool firstsMessages; 6 | final bool subscriptions; 7 | final bool bitsDonations; 8 | final bool announcements; 9 | final bool incomingRaids; 10 | final bool redemptions; 11 | 12 | const ChatEventsSettings({ 13 | required this.firstsMessages, 14 | required this.subscriptions, 15 | required this.bitsDonations, 16 | required this.announcements, 17 | required this.incomingRaids, 18 | required this.redemptions, 19 | }); 20 | 21 | ChatEventsSettings copyWith({ 22 | bool? firstsMessages, 23 | bool? subscriptions, 24 | bool? bitsDonations, 25 | bool? announcements, 26 | bool? incomingRaids, 27 | bool? redemptions, 28 | }) { 29 | return ChatEventsSettings( 30 | firstsMessages: firstsMessages ?? this.firstsMessages, 31 | subscriptions: subscriptions ?? this.subscriptions, 32 | bitsDonations: bitsDonations ?? this.bitsDonations, 33 | announcements: announcements ?? this.announcements, 34 | incomingRaids: incomingRaids ?? this.incomingRaids, 35 | redemptions: redemptions ?? this.redemptions, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/streamelements/get_last_activities_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/entities/stream_elements/se_activity.dart'; 5 | import 'package:irllink/src/domain/repositories/streamelements_repository.dart'; 6 | 7 | class StreamElementsGetLastActivitiesParams { 8 | final String token; 9 | final String channel; 10 | 11 | StreamElementsGetLastActivitiesParams({ 12 | required this.token, 13 | required this.channel, 14 | }); 15 | } 16 | 17 | class StreamElementsGetLastActivitiesUseCase 18 | implements 19 | UseCase>, 20 | StreamElementsGetLastActivitiesParams> { 21 | final StreamelementsRepository streamelementsRepository; 22 | 23 | StreamElementsGetLastActivitiesUseCase({ 24 | required this.streamelementsRepository, 25 | }); 26 | 27 | @override 28 | Future>> call({ 29 | required StreamElementsGetLastActivitiesParams params, 30 | }) { 31 | return streamelementsRepository.getLastActivities( 32 | params.token, 33 | params.channel, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/core/utils/pkce_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:math'; 3 | 4 | import 'package:crypto/crypto.dart'; 5 | 6 | class PKCEUtils { 7 | /// Generates a random code verifier for PKCE 8 | /// 9 | /// The code verifier is a random string between 43 and 128 characters long, 10 | /// containing only the characters [A-Z], [a-z], [0-9], "-", ".", "_", and "~". 11 | static String generateCodeVerifier() { 12 | const String chars = 13 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; 14 | final random = Random.secure(); 15 | final length = random.nextInt(86) + 43; // Random length between 43 and 128 16 | 17 | return List.generate(length, (index) => chars[random.nextInt(chars.length)]) 18 | .join(); 19 | } 20 | 21 | /// Generates a code challenge from a code verifier using SHA-256 22 | /// 23 | /// The code challenge is the base64url-encoded SHA-256 hash of the code verifier, 24 | /// with padding characters removed. 25 | static String generateCodeChallenge(String codeVerifier) { 26 | final bytes = utf8.encode(codeVerifier); 27 | final digest = sha256.convert(bytes); 28 | final base64Url = base64UrlEncode(digest.bytes); 29 | return base64Url.replaceAll('=', ''); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_user_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'twitch_user_dto.freezed.dart'; 4 | part 'twitch_user_dto.g.dart'; 5 | 6 | @freezed 7 | abstract class TwitchUserDTO with _$TwitchUserDTO { 8 | const factory TwitchUserDTO({ 9 | required String id, 10 | required String login, 11 | @JsonKey(name: 'display_name') required String displayName, 12 | @JsonKey(name: 'broadcaster_type') required String broadcasterType, 13 | required String description, 14 | @JsonKey(name: 'profile_image_url') required String profileImageUrl, 15 | @JsonKey(name: 'view_count', fromJson: _stringToInt) required int viewCount, 16 | }) = _TwitchUserDTO; 17 | 18 | factory TwitchUserDTO.fromJson(Map json) => 19 | _$TwitchUserDTOFromJson(json); 20 | } 21 | 22 | // Because in previous versions of the app, the viewCount was stored as a string (even tho it made no sense to save this in local storage) 23 | // ignore: avoid_annotating_with_dynamic 24 | int _stringToInt(dynamic json) { 25 | if (json is String) { 26 | return int.tryParse(json) ?? 0; // Fallback to 0 if parsing fails 27 | } else if (json is int) { 28 | return json; 29 | } 30 | throw Exception("Unexpected type"); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/core/utils/init_dio.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:get/get_core/src/get_main.dart'; 3 | import 'package:get/get_instance/get_instance.dart'; 4 | import 'package:irllink/src/core/services/talker_service.dart'; 5 | 6 | import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart'; 7 | import 'package:talker_dio_logger/talker_dio_logger_settings.dart'; 8 | import 'package:talker_flutter/talker_flutter.dart'; 9 | 10 | Dio initDio(String? baseUrl) { 11 | Talker talker = Get.find().talker; 12 | var dio = Dio( 13 | BaseOptions( 14 | baseUrl: baseUrl ?? '', 15 | headers: { 16 | 'Content-Type': 'application/json', 17 | 'Accept': 'application/json', 18 | }, 19 | validateStatus: (status) => 20 | status != null && status >= 200 && status < 300, 21 | ), 22 | ); 23 | dio.interceptors.add( 24 | TalkerDioLogger( 25 | talker: talker, 26 | settings: TalkerDioLoggerSettings( 27 | requestFilter: (RequestOptions options) => 28 | !options.path.contains('api.twitch.tv'), 29 | printRequestHeaders: true, 30 | responseFilter: (response) => ![200, 202].contains(response.statusCode), 31 | ), 32 | ), 33 | ); 34 | 35 | return dio; 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/browser_tab_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | 3 | part 'browser_tab_settings_dto.freezed.dart'; 4 | part 'browser_tab_settings_dto.g.dart'; 5 | 6 | // Custom converter for boolean values stored as integers in SQLite 7 | class BoolToIntConverter implements JsonConverter { 8 | const BoolToIntConverter(); 9 | 10 | @override 11 | bool fromJson(Object json) { 12 | if (json is bool) { 13 | return json; 14 | } 15 | if (json is int) { 16 | return json == 1; 17 | } 18 | if (json is String) { 19 | return json.toLowerCase() == 'true'; 20 | } 21 | return false; 22 | } 23 | 24 | @override 25 | Object toJson(bool object) => object; 26 | } 27 | 28 | @freezed 29 | abstract class BrowserTabDTO with _$BrowserTabDTO { 30 | const factory BrowserTabDTO({ 31 | required String id, 32 | required String title, 33 | required String url, 34 | @BoolToIntConverter() required bool toggled, 35 | @JsonKey(name: 'is_ios_audio_source') 36 | @BoolToIntConverter() 37 | required bool iOSAudioSource, 38 | }) = _BrowserTabDTO; 39 | 40 | factory BrowserTabDTO.fromJson(Map json) => 41 | _$BrowserTabDTOFromJson(json); 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/core/services/settings_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:irllink/src/domain/entities/settings.dart'; 4 | import 'package:irllink/src/domain/usecases/settings/get_settings_usecase.dart'; 5 | import 'package:irllink/src/domain/usecases/settings/set_settings_usecase.dart'; 6 | 7 | class SettingsService extends GetxService { 8 | SettingsService({ 9 | required this.getSettingsUseCase, 10 | required this.setSettingsUseCase, 11 | }); 12 | 13 | final GetSettingsUseCase getSettingsUseCase; 14 | final SetSettingsUseCase setSettingsUseCase; 15 | 16 | late Rx settings; 17 | 18 | Future init() async { 19 | await getSettings(); 20 | return this; 21 | } 22 | 23 | Future getSettings() async { 24 | final settingsResult = await getSettingsUseCase(); 25 | 26 | settingsResult.fold( 27 | (l) => debugPrint(l.message), 28 | (r) => settings = r.obs, 29 | ); 30 | } 31 | 32 | Future saveSettings() async { 33 | settings.refresh(); 34 | final setResult = await setSettingsUseCase( 35 | params: settings.value, 36 | ); 37 | setResult.fold( 38 | (l) => debugPrint(l.message), 39 | (r) => {}, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/chat_settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:irllink/src/domain/entities/chat/chat_message.dart'; 3 | 4 | part 'chat_settings_dto.freezed.dart'; 5 | part 'chat_settings_dto.g.dart'; 6 | 7 | @freezed 8 | abstract class ChatSettingsDTO with _$ChatSettingsDTO { 9 | factory ChatSettingsDTO({ 10 | @Default(true) bool hideDeletedMessages, 11 | }) = _ChatSettingsDTO; 12 | ChatSettingsDTO._(); 13 | 14 | factory ChatSettingsDTO.blank() => ChatSettingsDTO(); 15 | factory ChatSettingsDTO.fromJson(Map json) => 16 | _$ChatSettingsDTOFromJson(json); 17 | } 18 | 19 | @freezed 20 | abstract class ChatGroupDTO with _$ChatGroupDTO { 21 | const factory ChatGroupDTO({ 22 | required String id, 23 | required List channels, 24 | }) = _ChatGroupDTO; 25 | 26 | factory ChatGroupDTO.fromJson(Map json) => 27 | _$ChatGroupDTOFromJson(json); 28 | } 29 | 30 | @freezed 31 | abstract class ChannelDTO with _$ChannelDTO { 32 | const factory ChannelDTO({ 33 | required Platform platform, 34 | required String channel, 35 | }) = _ChannelDTO; 36 | 37 | factory ChannelDTO.fromJson(Map json) => 38 | _$ChannelDTOFromJson(json); 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/domain/usecases/twitch/end_prediction_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:dartz/dartz.dart'; 2 | import 'package:irllink/src/core/failure.dart'; 3 | import 'package:irllink/src/core/usecases/usecase.dart'; 4 | import 'package:irllink/src/domain/repositories/twitch_repository.dart'; 5 | 6 | class EndPredictionUseCaseParams { 7 | final String accessToken; 8 | final String broadcasterId; 9 | final String predictionId; 10 | final String status; 11 | final String? winningOutcomeId; 12 | 13 | EndPredictionUseCaseParams({ 14 | required this.accessToken, 15 | required this.broadcasterId, 16 | required this.predictionId, 17 | required this.status, 18 | this.winningOutcomeId, 19 | }); 20 | } 21 | 22 | class EndPredictionUseCase 23 | implements UseCase, EndPredictionUseCaseParams> { 24 | final TwitchRepository twitchRepository; 25 | 26 | EndPredictionUseCase({required this.twitchRepository}); 27 | 28 | @override 29 | Future> call({ 30 | required EndPredictionUseCaseParams params, 31 | }) { 32 | return twitchRepository.endPrediction( 33 | params.accessToken, 34 | params.broadcasterId, 35 | params.predictionId, 36 | params.status, 37 | params.winningOutcomeId, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings_dto.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:irllink/src/data/entities/settings/chat_events_settings_dto.dart'; 3 | import 'package:irllink/src/data/entities/settings/chat_settings_dto.dart'; 4 | import 'package:irllink/src/data/entities/settings/general_settings_dto.dart'; 5 | 6 | part 'settings_dto.freezed.dart'; 7 | part 'settings_dto.g.dart'; 8 | 9 | @freezed 10 | abstract class SettingsDTO with _$SettingsDTO { 11 | const factory SettingsDTO({ 12 | //CHAT SETTINGS 13 | @Default(true) bool isEmotes, 14 | @Default(19) double textSize, 15 | @Default(false) bool displayTimestamp, 16 | required ChatEventsSettingsDTO chatEventsSettings, 17 | required ChatSettingsDTO chatSettings, 18 | //GENERAL SETTINGS 19 | required GeneralSettingsDTO generalSettings, 20 | //CONNECTIONS SETTINGS 21 | @Default("") String rtIrlPushKey, 22 | }) = _SettingsDTO; 23 | 24 | factory SettingsDTO.blank() => SettingsDTO( 25 | chatEventsSettings: ChatEventsSettingsDTO.blank(), 26 | chatSettings: ChatSettingsDTO.blank(), 27 | generalSettings: GeneralSettingsDTO.blank(), 28 | ); 29 | factory SettingsDTO.fromJson(Map json) => 30 | _$SettingsDTOFromJson(json); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/domain/entities/kick/kick_channel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:irllink/src/domain/entities/kick/kick_category.dart'; 3 | 4 | @immutable 5 | class KickChannel { 6 | final String bannerPicture; 7 | final int broadcasterUserId; 8 | final KickCategory category; 9 | final String channelDescription; 10 | final String slug; 11 | final KickChannelStream stream; 12 | final String streamTitle; 13 | 14 | const KickChannel({ 15 | required this.bannerPicture, 16 | required this.broadcasterUserId, 17 | required this.category, 18 | required this.channelDescription, 19 | required this.slug, 20 | required this.stream, 21 | required this.streamTitle, 22 | }); 23 | } 24 | 25 | @immutable 26 | class KickChannelStream { 27 | final bool isLive; 28 | final bool isMature; 29 | final String key; 30 | final String language; 31 | final String startTime; 32 | final String thumbnail; 33 | final String url; 34 | final int viewerCount; 35 | 36 | const KickChannelStream({ 37 | required this.isLive, 38 | required this.isMature, 39 | required this.key, 40 | required this.language, 41 | required this.startTime, 42 | required this.thumbnail, 43 | required this.url, 44 | required this.viewerCount, 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/chat_events_settings_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'chat_events_settings_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _ChatEventsSettingsDTO _$ChatEventsSettingsDTOFromJson( 10 | Map json) => 11 | _ChatEventsSettingsDTO( 12 | firstsMessages: json['firstsMessages'] as bool? ?? true, 13 | subscriptions: json['subscriptions'] as bool? ?? true, 14 | bitsDonations: json['bitsDonations'] as bool? ?? true, 15 | announcements: json['announcements'] as bool? ?? true, 16 | incomingRaids: json['incomingRaids'] as bool? ?? true, 17 | redemptions: json['redemptions'] as bool? ?? true, 18 | ); 19 | 20 | Map _$ChatEventsSettingsDTOToJson( 21 | _ChatEventsSettingsDTO instance) => 22 | { 23 | 'firstsMessages': instance.firstsMessages, 24 | 'subscriptions': instance.subscriptions, 25 | 'bitsDonations': instance.bitsDonations, 26 | 'announcements': instance.announcements, 27 | 'incomingRaids': instance.incomingRaids, 28 | 'redemptions': instance.redemptions, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/src/data/entities/twitch/twitch_credentials_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'twitch_credentials_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _TwitchCredentialsDTO _$TwitchCredentialsDTOFromJson( 10 | Map json) => 11 | _TwitchCredentialsDTO( 12 | accessToken: json['accessToken'] as String, 13 | idToken: json['idToken'] as String, 14 | refreshToken: json['refreshToken'] as String, 15 | expiresIn: json['expiresIn'] as String, 16 | decodedIdToken: _stringToTwitchDecodedIdTokenDTO(json['decodedIdToken']), 17 | twitchUser: _stringToTwitchUserDTO(json['twitchUser']), 18 | scopes: json['scopes'] as String, 19 | ); 20 | 21 | Map _$TwitchCredentialsDTOToJson( 22 | _TwitchCredentialsDTO instance) => 23 | { 24 | 'accessToken': instance.accessToken, 25 | 'idToken': instance.idToken, 26 | 'refreshToken': instance.refreshToken, 27 | 'expiresIn': instance.expiresIn, 28 | 'decodedIdToken': instance.decodedIdToken, 29 | 'twitchUser': instance.twitchUser, 30 | 'scopes': instance.scopes, 31 | }; 32 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings/general_settings_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'general_settings_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _GeneralSettingsDTO _$GeneralSettingsDTOFromJson(Map json) => 10 | _GeneralSettingsDTO( 11 | isDarkMode: json['isDarkMode'] as bool? ?? true, 12 | keepSpeakerOn: json['keepSpeakerOn'] as bool? ?? true, 13 | displayViewerCount: json['displayViewerCount'] as bool? ?? true, 14 | appLanguage: json['appLanguage'] as Map? ?? 15 | const {"languageCode": "en", "countryCode": "US"}, 16 | splitViewWeights: (json['splitViewWeights'] as List?) 17 | ?.map((e) => (e as num).toDouble()) 18 | .toList() ?? 19 | const [0.5, 0.5], 20 | ); 21 | 22 | Map _$GeneralSettingsDTOToJson(_GeneralSettingsDTO instance) => 23 | { 24 | 'isDarkMode': instance.isDarkMode, 25 | 'keepSpeakerOn': instance.keepSpeakerOn, 26 | 'displayViewerCount': instance.displayViewerCount, 27 | 'appLanguage': instance.appLanguage, 28 | 'splitViewWeights': instance.splitViewWeights, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/src/domain/entities/twitch/twitch_hype_train.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | class TwitchHypeTrain { 5 | final String id; 6 | final int total; 7 | final int progress; 8 | final int goal; 9 | final int level; 10 | final List topContributions; 11 | final Contribution? lastContribution; 12 | final DateTime endsAt; 13 | 14 | const TwitchHypeTrain({ 15 | required this.id, 16 | required this.total, 17 | required this.progress, 18 | required this.goal, 19 | required this.level, 20 | required this.topContributions, 21 | required this.lastContribution, 22 | required this.endsAt, 23 | }); 24 | 25 | factory TwitchHypeTrain.empty() { 26 | return TwitchHypeTrain( 27 | id: '', 28 | total: 0, 29 | progress: 0, 30 | goal: 0, 31 | level: 0, 32 | topContributions: const [], 33 | endsAt: DateTime.now(), 34 | lastContribution: null, 35 | ); 36 | } 37 | } 38 | 39 | @immutable 40 | class Contribution { 41 | final String userId; 42 | final String userLogin; 43 | final String userName; 44 | final String type; 45 | final int total; 46 | 47 | const Contribution({ 48 | required this.userId, 49 | required this.userLogin, 50 | required this.userName, 51 | required this.type, 52 | required this.total, 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/presentation/widgets/hype_train.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:irllink/src/core/utils/print_duration.dart'; 4 | import 'package:irllink/src/domain/entities/twitch/twitch_hype_train.dart'; 5 | 6 | Widget hypeTrain( 7 | BuildContext context, 8 | TwitchHypeTrain? hypetrain, 9 | Duration? remainingTime, 10 | ) { 11 | if (hypetrain == null || hypetrain.id == '') { 12 | return Container(); 13 | } 14 | return Row( 15 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 16 | children: [ 17 | Wrap( 18 | crossAxisAlignment: WrapCrossAlignment.center, 19 | children: [ 20 | Container( 21 | padding: 22 | const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), 23 | decoration: const BoxDecoration( 24 | color: Colors.deepPurple, 25 | borderRadius: BorderRadius.all( 26 | Radius.circular(8), 27 | ), 28 | ), 29 | child: Text('LVL ${hypetrain.level}'), 30 | ), 31 | const SizedBox( 32 | width: 10, 33 | ), 34 | Text('hype_train'.tr), 35 | ], 36 | ), 37 | Text('${hypetrain.progress}%'), 38 | Text( 39 | printDuration( 40 | remainingTime ?? Duration.zero, 41 | ), 42 | ), 43 | ], 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/bindings/settings/hidden_users_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:irllink/src/core/services/talker_service.dart'; 3 | import 'package:irllink/src/data/datasources/local/settings_local_data_source.dart'; 4 | import 'package:irllink/src/data/repositories/settings_repository_impl.dart'; 5 | import 'package:irllink/src/domain/usecases/settings/get_hidden_users_usecase.dart'; 6 | import 'package:irllink/src/domain/usecases/settings/remove_hidden_user_usecase.dart'; 7 | import 'package:irllink/src/presentation/controllers/settings/hidden_users_settings_controller.dart'; 8 | 9 | class HiddenUsersSettingsBinding extends Bindings { 10 | @override 11 | void dependencies() { 12 | final talker = Get.find().talker; 13 | final settingsRepository = SettingsRepositoryImpl( 14 | localDataSource: SettingsLocalDataSourceImpl( 15 | talker: talker, 16 | ), 17 | talker: talker, 18 | ); 19 | 20 | final getHiddenUsersUseCase = GetHiddenUsersUseCase( 21 | settingsRepository: settingsRepository, 22 | ); 23 | final removeHiddenUserUseCase = RemoveHiddenUserUseCase( 24 | settingsRepository: settingsRepository, 25 | ); 26 | 27 | Get.lazyPut( 28 | () => HiddenUsersSettingsController( 29 | getHiddenUsersUseCase: getHiddenUsersUseCase, 30 | removeHiddenUserUseCase: removeHiddenUserUseCase, 31 | ), 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build Android 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-latest 12 | permissions: 13 | contents: read 14 | packages: write 15 | 16 | steps: 17 | - name: Test connection 18 | run: curl "https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json" 19 | 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Java environment 24 | uses: actions/setup-java@v4 25 | with: 26 | distribution: "zulu" 27 | java-version: "17" 28 | 29 | - name: Set up Flutter environment 30 | uses: subosito/flutter-action@v2 31 | with: 32 | channel: 'stable' 33 | 34 | - name: Add google-services.json 35 | env: 36 | GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} 37 | run: echo "$GOOGLE_SERVICES_JSON" | base64 -d > ./android/app/google-services.json 38 | 39 | - name: Enable Swift Package Manager 40 | run: flutter config --enable-swift-package-manager 41 | 42 | - name: Build APK and App Bundle 43 | run: | 44 | flutter config --enable-swift-package-manager 45 | flutter pub get 46 | flutter build apk --debug 47 | flutter build appbundle --debug 48 | -------------------------------------------------------------------------------- /lib/src/domain/entities/twitch/twitch_prediction.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum PredictionStatus { 4 | empty, 5 | resolved, 6 | active, 7 | canceled, 8 | locked, 9 | } 10 | 11 | @immutable 12 | class TwitchPrediction { 13 | final String id; 14 | final String title; 15 | final String winningOutcomeId; 16 | final int totalUsers; 17 | final List outcomes; 18 | final PredictionStatus status; 19 | final DateTime remainingTime; 20 | 21 | const TwitchPrediction({ 22 | required this.id, 23 | required this.title, 24 | required this.winningOutcomeId, 25 | required this.totalUsers, 26 | required this.outcomes, 27 | required this.status, 28 | required this.remainingTime, 29 | }); 30 | 31 | factory TwitchPrediction.empty() { 32 | return TwitchPrediction( 33 | id: '', 34 | title: '', 35 | winningOutcomeId: '', 36 | totalUsers: 0, 37 | outcomes: const [], 38 | status: PredictionStatus.empty, 39 | remainingTime: DateTime.now(), 40 | ); 41 | } 42 | } 43 | 44 | @immutable 45 | class Outcome { 46 | final String id; 47 | final String title; 48 | final int users; 49 | final int channelPoints; 50 | final Color color; 51 | 52 | const Outcome({ 53 | required this.id, 54 | required this.title, 55 | required this.users, 56 | required this.channelPoints, 57 | required this.color, 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/core/resources/app_translations.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:get/get.dart'; 5 | 6 | const List> supportedLanguages = [ 7 | {"name": "English", "languageCode": "en", "countryCode": "US"}, 8 | {"name": "Français ", "languageCode": "fr", "countryCode": "FR"}, 9 | {"name": "繁體中文", "languageCode": "zh", "countryCode": "TW"}, 10 | {"name": "Español", "languageCode": "es", "countryCode": "ES"}, 11 | {"name": "Italiano", "languageCode": "it", "countryCode": "IT"}, 12 | {"name": "Русский", "languageCode": "ru", "countryCode": "RU"}, 13 | ]; 14 | 15 | class AppTranslations extends Translations { 16 | @override 17 | Map> get keys => {}; 18 | 19 | static Future initLanguages() async { 20 | final keys = await readJson(); 21 | Get.clearTranslations(); 22 | Get.addTranslations(keys); 23 | } 24 | 25 | static Future>> readJson() async { 26 | final keys = >{}; 27 | 28 | await Future.forEach(supportedLanguages, (element) async { 29 | String key = '${element['languageCode']!}_${element['countryCode']!}'; 30 | final res = await rootBundle.loadString('./lib/assets/i18n/$key.json'); 31 | Map data = Map.castFrom(jsonDecode(res)); 32 | keys.addAll({key: data}); 33 | }); 34 | return keys; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/bindings/settings/obs_settings_bindings.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:irllink/src/core/services/talker_service.dart' 3 | show TalkerService; 4 | import 'package:irllink/src/data/datasources/local/settings_local_data_source.dart'; 5 | import 'package:irllink/src/data/repositories/settings_repository_impl.dart'; 6 | import 'package:irllink/src/domain/usecases/obs/get_obs_credentials_usecase.dart'; 7 | import 'package:irllink/src/domain/usecases/obs/update_obs_url_usecase.dart'; 8 | import 'package:irllink/src/presentation/controllers/settings/obs_settings_controller.dart'; 9 | 10 | class ObsSettingsBindings extends Bindings { 11 | @override 12 | void dependencies() { 13 | final talker = Get.find().talker; 14 | final settingsRepository = SettingsRepositoryImpl( 15 | localDataSource: SettingsLocalDataSourceImpl( 16 | talker: talker, 17 | ), 18 | talker: talker, 19 | ); 20 | 21 | final getObsCredentialsUsecase = GetObsCredentialsUsecase( 22 | settingsRepository: settingsRepository, 23 | ); 24 | 25 | final updateObsSettingsUsecase = UpdateObsSettingsUsecase( 26 | settingsRepository: settingsRepository, 27 | ); 28 | 29 | Get.lazyPut( 30 | () => ObsSettingsController( 31 | getObsCredentialsUsecase: getObsCredentialsUsecase, 32 | updateObsSettingsUsecase: updateObsSettingsUsecase, 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/data/entities/settings_dto.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'settings_dto.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _SettingsDTO _$SettingsDTOFromJson(Map json) => _SettingsDTO( 10 | isEmotes: json['isEmotes'] as bool? ?? true, 11 | textSize: (json['textSize'] as num?)?.toDouble() ?? 19, 12 | displayTimestamp: json['displayTimestamp'] as bool? ?? false, 13 | chatEventsSettings: ChatEventsSettingsDTO.fromJson( 14 | json['chatEventsSettings'] as Map), 15 | chatSettings: ChatSettingsDTO.fromJson( 16 | json['chatSettings'] as Map), 17 | generalSettings: GeneralSettingsDTO.fromJson( 18 | json['generalSettings'] as Map), 19 | rtIrlPushKey: json['rtIrlPushKey'] as String? ?? "", 20 | ); 21 | 22 | Map _$SettingsDTOToJson(_SettingsDTO instance) => 23 | { 24 | 'isEmotes': instance.isEmotes, 25 | 'textSize': instance.textSize, 26 | 'displayTimestamp': instance.displayTimestamp, 27 | 'chatEventsSettings': instance.chatEventsSettings, 28 | 'chatSettings': instance.chatSettings, 29 | 'generalSettings': instance.generalSettings, 30 | 'rtIrlPushKey': instance.rtIrlPushKey, 31 | }; 32 | -------------------------------------------------------------------------------- /android/app/src/androidTest/java/dev/lezd/www/irllink/MainActivityTest.java: -------------------------------------------------------------------------------- 1 | package dev.lezd.www.irllink; // replace "com.example.myapp" with your app's package 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.Parameterized; 7 | import org.junit.runners.Parameterized.Parameters; 8 | import pl.leancode.patrol.PatrolJUnitRunner; 9 | 10 | @RunWith(Parameterized.class) 11 | public class MainActivityTest { 12 | @Parameters(name = "{0}") 13 | public static Object[] testCases() { 14 | PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); 15 | // replace "MainActivity.class" with "io.flutter.embedding.android.FlutterActivity.class" 16 | // if in AndroidManifest.xml in manifest/application/activity you have 17 | // android:name="io.flutter.embedding.android.FlutterActivity" 18 | instrumentation.setUp(MainActivity.class); 19 | instrumentation.waitForPatrolAppService(); 20 | return instrumentation.listDartTests(); 21 | } 22 | 23 | public MainActivityTest(String dartTestName) { 24 | this.dartTestName = dartTestName; 25 | } 26 | 27 | private final String dartTestName; 28 | 29 | @Test 30 | public void runDartTest() { 31 | PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation(); 32 | instrumentation.runDartTest(dartTestName); 33 | } 34 | } 35 | --------------------------------------------------------------------------------