├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── update-translations.yml ├── .gitignore ├── .idea ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml └── zulip.iml ├── .mailmap ├── .metadata ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── .idea │ └── inspectionProfiles │ │ └── Project_Default.xml ├── app │ ├── build.gradle │ ├── lint-baseline.xml │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ └── com │ │ │ └── zulip │ │ │ └── flutter │ │ │ ├── MainActivity.kt │ │ │ ├── Notifications.g.kt │ │ │ └── ZulipPlugin.kt │ │ └── res │ │ ├── drawable-hdpi │ │ └── zulip_notification.webp │ │ ├── drawable-mdpi │ │ └── zulip_notification.webp │ │ ├── drawable-xhdpi │ │ └── zulip_notification.webp │ │ ├── drawable-xxhdpi │ │ └── zulip_notification.webp │ │ ├── drawable-xxxhdpi │ │ └── zulip_notification.webp │ │ ├── drawable │ │ └── launch_background.xml │ │ ├── mipmap-anydpi │ │ └── ic_launcher.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher_background.webp │ │ └── ic_launcher_monochrome.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher_background.webp │ │ └── ic_launcher_monochrome.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher_background.webp │ │ └── ic_launcher_monochrome.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher_background.webp │ │ └── ic_launcher_monochrome.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher_background.webp │ │ └── ic_launcher_monochrome.webp │ │ ├── raw │ │ ├── chime2.m4a │ │ ├── chime3.m4a │ │ ├── chime4.m4a │ │ └── keep.xml │ │ ├── values-night │ │ └── styles.xml │ │ └── values │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── KaTeX │ ├── KaTeX_AMS-Regular.ttf │ ├── KaTeX_Caligraphic-Bold.ttf │ ├── KaTeX_Caligraphic-Regular.ttf │ ├── KaTeX_Fraktur-Bold.ttf │ ├── KaTeX_Fraktur-Regular.ttf │ ├── KaTeX_Main-Bold.ttf │ ├── KaTeX_Main-BoldItalic.ttf │ ├── KaTeX_Main-Italic.ttf │ ├── KaTeX_Main-Regular.ttf │ ├── KaTeX_Math-BoldItalic.ttf │ ├── KaTeX_Math-Italic.ttf │ ├── KaTeX_SansSerif-Bold.ttf │ ├── KaTeX_SansSerif-Italic.ttf │ ├── KaTeX_SansSerif-Regular.ttf │ ├── KaTeX_Script-Regular.ttf │ ├── KaTeX_Size1-Regular.ttf │ ├── KaTeX_Size2-Regular.ttf │ ├── KaTeX_Size3-Regular.ttf │ ├── KaTeX_Size4-Regular.ttf │ ├── KaTeX_Typewriter-Regular.ttf │ └── LICENSE ├── Noto_Color_Emoji │ ├── LICENSE │ └── Noto-COLRv1.ttf ├── Pygments │ ├── AUTHORS.txt │ └── LICENSE.txt ├── Source_Code_Pro │ ├── LICENSE.md │ ├── SourceCodeVF-Italic.otf │ └── SourceCodeVF-Upright.otf ├── Source_Sans_3 │ ├── LICENSE.md │ ├── SourceSans3VF-Italic.otf │ └── SourceSans3VF-Upright.otf ├── app-icons │ ├── zulip-beta-combined.svg │ ├── zulip-combined.svg │ ├── zulip-gradient.svg │ ├── zulip-white-z-beta-on-transparent.svg │ └── zulip-white-z-on-transparent.svg ├── icons │ ├── ZulipIcons.ttf │ ├── arrow_down.svg │ ├── arrow_left_right.svg │ ├── arrow_right.svg │ ├── at_sign.svg │ ├── attach_file.svg │ ├── bot.svg │ ├── camera.svg │ ├── check.svg │ ├── check_remove.svg │ ├── chevron_right.svg │ ├── clock.svg │ ├── contacts.svg │ ├── copy.svg │ ├── edit.svg │ ├── follow.svg │ ├── format_quote.svg │ ├── globe.svg │ ├── group_dm.svg │ ├── hash_italic.svg │ ├── hash_sign.svg │ ├── image.svg │ ├── inbox.svg │ ├── info.svg │ ├── inherit.svg │ ├── language.svg │ ├── lock.svg │ ├── menu.svg │ ├── message_checked.svg │ ├── message_feed.svg │ ├── mute.svg │ ├── read_receipts.svg │ ├── send.svg │ ├── settings.svg │ ├── share.svg │ ├── share_ios.svg │ ├── smile.svg │ ├── star.svg │ ├── star_filled.svg │ ├── three_person.svg │ ├── topic.svg │ ├── topics.svg │ ├── unmute.svg │ └── user.svg └── l10n │ ├── app_ar.arb │ ├── app_de.arb │ ├── app_en.arb │ ├── app_en_GB.arb │ ├── app_ja.arb │ ├── app_nb.arb │ ├── app_pl.arb │ ├── app_ru.arb │ ├── app_sk.arb │ ├── app_uk.arb │ ├── app_zh.arb │ ├── app_zh_Hans_CN.arb │ └── app_zh_Hant_TW.arb ├── build.yaml ├── docs ├── changelog.md ├── integration_tests.md ├── release.md ├── setup.md └── translation.md ├── integration_test ├── perf_driver.dart └── unreadmarker_test.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Zulip.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-1024x1024@1x.png │ │ ├── Icon-20x20@2x.png │ │ ├── Icon-20x20@3x.png │ │ ├── Icon-29x29@2x.png │ │ ├── Icon-29x29@3x.png │ │ ├── Icon-40x40@2x.png │ │ ├── Icon-40x40@3x.png │ │ ├── Icon-60x60@2x.png │ │ ├── Icon-60x60@3x.png │ │ ├── Icon-76x76@2x.png │ │ └── Icon-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── Runner-Bridging-Header.h │ └── Runner.entitlements ├── l10n.yaml ├── lib ├── api │ ├── backoff.dart │ ├── core.dart │ ├── exception.dart │ ├── model │ │ ├── events.dart │ │ ├── events.g.dart │ │ ├── initial_snapshot.dart │ │ ├── initial_snapshot.g.dart │ │ ├── json.dart │ │ ├── model.dart │ │ ├── model.g.dart │ │ ├── narrow.dart │ │ ├── narrow.g.dart │ │ ├── reaction.dart │ │ ├── reaction.g.dart │ │ ├── submessage.dart │ │ ├── submessage.g.dart │ │ └── web_auth.dart │ ├── notifications.dart │ ├── notifications.g.dart │ └── route │ │ ├── account.dart │ │ ├── account.g.dart │ │ ├── channels.dart │ │ ├── channels.g.dart │ │ ├── events.dart │ │ ├── events.g.dart │ │ ├── messages.dart │ │ ├── messages.g.dart │ │ ├── notifications.dart │ │ ├── realm.dart │ │ ├── realm.g.dart │ │ ├── saved_snippets.dart │ │ ├── saved_snippets.g.dart │ │ ├── submessage.dart │ │ ├── typing.dart │ │ ├── users.dart │ │ └── users.g.dart ├── example │ └── sticky_header.dart ├── firebase_options.dart ├── generated │ └── l10n │ │ ├── zulip_localizations.dart │ │ ├── zulip_localizations_ar.dart │ │ ├── zulip_localizations_de.dart │ │ ├── zulip_localizations_en.dart │ │ ├── zulip_localizations_ja.dart │ │ ├── zulip_localizations_nb.dart │ │ ├── zulip_localizations_pl.dart │ │ ├── zulip_localizations_ru.dart │ │ ├── zulip_localizations_sk.dart │ │ ├── zulip_localizations_uk.dart │ │ └── zulip_localizations_zh.dart ├── host │ ├── android_notifications.dart │ └── android_notifications.g.dart ├── licenses.dart ├── log.dart ├── main.dart ├── model │ ├── actions.dart │ ├── algorithms.dart │ ├── autocomplete.dart │ ├── avatar_url.dart │ ├── binding.dart │ ├── channel.dart │ ├── code_block.dart │ ├── compose.dart │ ├── content.dart │ ├── database.dart │ ├── database.g.dart │ ├── emoji.dart │ ├── internal_link.dart │ ├── internal_link.g.dart │ ├── katex.dart │ ├── localizations.dart │ ├── message.dart │ ├── message_list.dart │ ├── narrow.dart │ ├── recent_dm_conversations.dart │ ├── recent_senders.dart │ ├── saved_snippet.dart │ ├── schema_versions.g.dart │ ├── settings.dart │ ├── store.dart │ ├── typing_status.dart │ ├── unreads.dart │ └── user.dart ├── notifications │ ├── display.dart │ └── receive.dart └── widgets │ ├── about_zulip.dart │ ├── action_sheet.dart │ ├── actions.dart │ ├── app.dart │ ├── app_bar.dart │ ├── autocomplete.dart │ ├── button.dart │ ├── channel_colors.dart │ ├── code_block.dart │ ├── color.dart │ ├── compose_box.dart │ ├── content.dart │ ├── dialog.dart │ ├── emoji.dart │ ├── emoji_reaction.dart │ ├── home.dart │ ├── icons.dart │ ├── inbox.dart │ ├── input.dart │ ├── inset_shadow.dart │ ├── lightbox.dart │ ├── login.dart │ ├── message_list.dart │ ├── page.dart │ ├── poll.dart │ ├── profile.dart │ ├── recent_dm_conversations.dart │ ├── scrolling.dart │ ├── settings.dart │ ├── sticky_header.dart │ ├── store.dart │ ├── subscription_list.dart │ ├── text.dart │ ├── theme.dart │ ├── topic_list.dart │ └── unread_count_badge.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── packages └── zulip_plugin │ ├── .metadata │ ├── android │ ├── build.gradle │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── zulip │ │ └── flutter │ │ └── ZulipShimPlugin.kt │ └── pubspec.yaml ├── pigeon └── notifications.dart ├── pubspec.lock ├── pubspec.yaml ├── shell.nix ├── test ├── api │ ├── backoff_test.dart │ ├── core_checks.dart │ ├── core_test.dart │ ├── exception_checks.dart │ ├── exception_test.dart │ ├── fake_api.dart │ ├── fake_api_test.dart │ ├── model │ │ ├── events_checks.dart │ │ ├── events_test.dart │ │ ├── initial_snapshot_test.dart │ │ ├── json_test.dart │ │ ├── model_checks.dart │ │ ├── model_test.dart │ │ ├── narrow_test.dart │ │ ├── reaction_test.dart │ │ ├── submessage_checks.dart │ │ ├── submessage_test.dart │ │ └── web_auth_test.dart │ ├── notifications_test.dart │ └── route │ │ ├── channels_test.dart │ │ ├── messages_test.dart │ │ ├── notifications_test.dart │ │ ├── realm_test.dart │ │ ├── route_checks.dart │ │ ├── saved_snippets_test.dart │ │ ├── submessage_test.dart │ │ └── typing_test.dart ├── example_data.dart ├── fake_async.dart ├── fake_async_checks.dart ├── fake_async_test.dart ├── flutter_checks.dart ├── flutter_test_config.dart ├── model │ ├── actions_test.dart │ ├── algorithms_test.dart │ ├── autocomplete_checks.dart │ ├── autocomplete_test.dart │ ├── avatar_url_test.dart │ ├── binding.dart │ ├── channel_test.dart │ ├── compose_test.dart │ ├── content_checks.dart │ ├── content_test.dart │ ├── database_test.dart │ ├── emoji_test.dart │ ├── internal_link_test.dart │ ├── message_checks.dart │ ├── message_list_test.dart │ ├── message_test.dart │ ├── narrow_checks.dart │ ├── narrow_test.dart │ ├── recent_dm_conversations_checks.dart │ ├── recent_dm_conversations_test.dart │ ├── recent_senders_test.dart │ ├── saved_snippet_test.dart │ ├── schemas │ │ ├── drift_schema_v1.json │ │ ├── drift_schema_v2.json │ │ ├── drift_schema_v3.json │ │ ├── drift_schema_v4.json │ │ ├── drift_schema_v5.json │ │ ├── drift_schema_v6.json │ │ ├── schema.dart │ │ ├── schema_v1.dart │ │ ├── schema_v2.dart │ │ ├── schema_v3.dart │ │ ├── schema_v4.dart │ │ ├── schema_v5.dart │ │ └── schema_v6.dart │ ├── settings_test.dart │ ├── store_checks.dart │ ├── store_test.dart │ ├── test_store.dart │ ├── typing_status_test.dart │ ├── unreads_checks.dart │ ├── unreads_test.dart │ └── user_test.dart ├── notifications │ ├── display_test.dart │ └── receive_test.dart ├── stdlib_checks.dart ├── test_async.dart ├── test_clipboard.dart ├── test_images.dart ├── test_navigation.dart ├── test_share_plus.dart └── widgets │ ├── action_sheet_test.dart │ ├── actions_test.dart │ ├── app_bar_test.dart │ ├── app_test.dart │ ├── autocomplete_test.dart │ ├── button_test.dart │ ├── channel_colors_checks.dart │ ├── channel_colors_test.dart │ ├── code_block_test.dart │ ├── color_test.dart │ ├── compose_box_checks.dart │ ├── compose_box_test.dart │ ├── content_checks.dart │ ├── content_test.dart │ ├── dialog_checks.dart │ ├── dialog_test.dart │ ├── emoji_reaction_test.dart │ ├── home_test.dart │ ├── inbox_test.dart │ ├── inset_shadow_test.dart │ ├── lightbox_test.dart │ ├── login_test.dart │ ├── message_list_checks.dart │ ├── message_list_test.dart │ ├── page_checks.dart │ ├── poll_test.dart │ ├── profile_page_checks.dart │ ├── profile_test.dart │ ├── recent_dm_conversations_test.dart │ ├── scrolling_test.dart │ ├── settings_test.dart │ ├── sticky_header_test.dart │ ├── store_checks.dart │ ├── store_test.dart │ ├── subscription_list_test.dart │ ├── test_app.dart │ ├── text_test.dart │ ├── theme_test.dart │ ├── topic_list_test.dart │ ├── unread_count_badge_checks.dart │ └── unread_count_badge_test.dart ├── tools ├── bump-version ├── check ├── content │ ├── check-features │ ├── fetch_messages.dart │ ├── model.dart │ └── unimplemented_features_test.dart ├── customer-testing │ └── setup.sh ├── format-changelog ├── generate-logos ├── gradle ├── icons │ ├── .gitignore │ ├── build-icon-font │ ├── build-icon-font.js │ ├── package-lock.json │ └── package.json ├── lib │ ├── cli.sh │ ├── ensure-coreutils.sh │ └── git.sh └── upgrade └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Suppress noisy generated files in diffs. 2 | 3 | # Dart files generated from the files next to them, or by Pigeon: 4 | *.g.dart -diff 5 | 6 | # Generated files for localizations. 7 | lib/generated/l10n/*.dart -diff 8 | 9 | # Data files for translations synced from Weblate: 10 | assets/l10n/app_*.arb -diff 11 | # though not the similar file that is the source for what to translate: 12 | assets/l10n/app_en.arb diff 13 | 14 | # Generated files for testing migrations: 15 | test/model/schemas/*.dart -diff 16 | test/model/schemas/*.json -diff 17 | 18 | # Kotlin files generated by, e.g., Pigeon: 19 | *.g.kt -diff 20 | 21 | # On the other hand, keep diffs for pubspec.lock. It contains 22 | # information independent of any non-generated file in the tree. 23 | # And thankfully it's much less verbose than, say, a yarn.lock. 24 | #pubspec.lock -diff 25 | 26 | 27 | # Treat SVG files as binary. They're XML, but typically 28 | # generated from a GUI editor and unenlightening to read. 29 | *.svg binary 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - uses: actions/checkout@v4.1.7 10 | 11 | - name: Install system dependencies 12 | run: TERM=dumb sudo apt install libsqlite3-dev -y 13 | 14 | - name: Set up JDK 15 | uses: actions/setup-java@v4.2.1 16 | with: 17 | java-version: 17 18 | distribution: temurin 19 | 20 | - name: Clone Flutter SDK 21 | # We can't do a depth-1 clone, because we need the most recent tag 22 | # so that Flutter knows its version and sees the constraint in our 23 | # pubspec is satisfied. It's uncommon for flutter/flutter to go 24 | # more than 100 commits between tags. Fetch 1000 for good measure. 25 | run: | 26 | git clone --depth=1000 -b main https://github.com/flutter/flutter ~/flutter 27 | TZ=UTC git --git-dir ~/flutter/.git log -1 --format='%h | %ci | %s' --date=iso8601-local 28 | echo ~/flutter/bin >> "$GITHUB_PATH" 29 | 30 | # The Flutter tool assumes the tip of tree is "origin/master" 31 | # (or "upstream/master"): 32 | # https://github.com/flutter/flutter/issues/160626 33 | # TODO(upstream): make workaround unneeded 34 | git --git-dir ~/flutter/.git update-ref refs/remotes/origin/master origin/main 35 | 36 | - name: Download Flutter SDK artifacts (flutter precache) 37 | run: flutter precache --universal 38 | 39 | - name: Download our dependencies (flutter pub get) 40 | run: flutter pub get 41 | 42 | - name: Run tools/check 43 | run: TERM=dumb tools/check --all --verbose 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | /.idea/* 20 | /android/.idea/* 21 | !/.idea/inspectionProfiles/ 22 | !/.idea/modules.xml 23 | !/.idea/zulip.iml 24 | !/android/.idea/inspectionProfiles/ 25 | 26 | # The .vscode folder contains launch configuration and tasks you configure in 27 | # VS Code which you may wish to be included in version control, so this line 28 | # is commented out by default. 29 | #.vscode/ 30 | 31 | # Flutter/Dart/Pub related 32 | **/doc/api/ 33 | **/ios/Flutter/.last_build_id 34 | .dart_tool/ 35 | /devtools_options.yaml 36 | .flutter-plugins 37 | .flutter-plugins-dependencies 38 | .packages 39 | .pub-cache/ 40 | .pub/ 41 | /build/ 42 | 43 | # Symbolication related 44 | app.*.symbols 45 | 46 | # Obfuscation related 47 | app.*.map.json 48 | 49 | # Android Studio will place build artifacts here 50 | /android/app/debug 51 | /android/app/profile 52 | /android/app/release 53 | 54 | # Old scaffolding hack 55 | lib/credential_fixture.dart 56 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/zulip.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # This file teaches `git log` and friends the canonical names 2 | # and email addresses to use for our contributors. 3 | # 4 | # For details on the format, see: 5 | # https://git.github.io/htmldocs/gitmailmap.html 6 | # 7 | # Handy commands for examining or adding to this file: 8 | # 9 | # # shows all names/emails after mapping, sorted: 10 | # $ git shortlog -es | sort -k2 11 | # 12 | # # shows raw names/emails, filtered by mapped name: 13 | # $ git log --format='%an %ae' --author=$NAME | uniq -c 14 | 15 | Chris Bobbe 16 | Greg Price 17 | K Akhil 18 | Lalit Kumar Singh 19 | Rajesh Malviya 20 | Shu Chen 21 | Tomlin7 22 | 23 | # The goal when editing this file is to group all of a given person's 24 | # contributions together, and under their preferred name and email 25 | # address. 26 | # 27 | # Where possible, to find out what name and email address to use for a 28 | # person, we ask them. 29 | # 30 | # In cases where we have several names or email addresses for one 31 | # person and we don't directly know their preferences, we make a guess 32 | # based on public information, with the following principles: 33 | # 34 | # * When making a guess, we always choose from among the names and 35 | # email addresses the person has used in the Git history. 36 | # 37 | # * If the contributions come from the same GitHub account, they're 38 | # the same person. 39 | # 40 | # * Prefer the name or email found in the contributor's GitHub profile 41 | # or their chat.zulip.org profile. 42 | # 43 | # * If one name looks like a full name and another a GitHub username, 44 | # prefer the full name. 45 | # 46 | # * If one email is at users.noreply.github.com, prefer any other. 47 | # 48 | # * Prefer the name or email the contributor has used more often 49 | # most recently. 50 | -------------------------------------------------------------------------------- /.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: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 8 | channel: main 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 17 | base_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 18 | - platform: android 19 | create_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 20 | base_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 21 | - platform: ios 22 | create_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 23 | base_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 24 | - platform: linux 25 | create_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 26 | base_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 27 | - platform: macos 28 | create_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 29 | base_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 30 | - platform: windows 31 | create_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 32 | base_revision: c9dd4584702dc5ab67a6dc9c6ebaa33739bac811 33 | 34 | # User provided section 35 | 36 | # List of Local paths (relative to this file) that should be 37 | # ignored by the migrate tool. 38 | # 39 | # Files that are not part of the templates will be ignored by default. 40 | unmanaged_files: 41 | - 'lib/main.dart' 42 | - 'ios/Runner.xcodeproj/project.pbxproj' 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Visual Studio Code settings for the zulip-flutter repo. 2 | { 3 | // VS Code formats files on save by default. 4 | // Like Flutter itself, we don't use the Dart autoformatter, 5 | // so disable that VS Code behavior. 6 | "[dart]": { 7 | // This appears redundant with the all-language setting, 8 | // but it's not: if the user's personal config has a Dart-specific 9 | // setting, then we need a Dart-specific setting to override it. 10 | "editor.formatOnSave": false, 11 | "editor.formatOnType": false, 12 | "editor.formatOnPaste": false, 13 | }, 14 | 15 | // For that matter, don't go reformatting whole files in any language. 16 | "editor.formatOnSave": false, 17 | 18 | // This much more focused automatic fix is helpful, though. 19 | "files.trimTrailingWhitespace": true, 20 | 21 | // Suppress the warnings that appear for TODOs throughout the codebase. 22 | "dart.showTodos": false, 23 | } 24 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # For docs on this file, see: 2 | # https://dart.dev/tools/analysis 3 | include: package:flutter_lints/flutter.yaml 4 | 5 | analyzer: 6 | language: 7 | strict-inference: true 8 | strict-raw-types: true 9 | strict-casts: true 10 | 11 | linter: 12 | # For a list of all available lints, with docs, see: 13 | # https://dart-lang.github.io/linter/lints/index.html. 14 | rules: 15 | always_declare_return_types: true 16 | no_literal_bool_comparisons: true 17 | prefer_relative_imports: true 18 | unnecessary_statements: true 19 | unawaited_futures: true 20 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | .cxx/ 10 | 11 | *keystore.properties 12 | *.keystore 13 | *.keystore.pgp 14 | *.jks 15 | -------------------------------------------------------------------------------- /android/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /android/app/lint-baseline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/zulip/flutter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.zulip.flutter 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/zulip_notification.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/drawable-hdpi/zulip_notification.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/zulip_notification.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/drawable-mdpi/zulip_notification.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/zulip_notification.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/drawable-xhdpi/zulip_notification.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/zulip_notification.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/drawable-xxhdpi/zulip_notification.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/zulip_notification.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/drawable-xxxhdpi/zulip_notification.webp -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp -------------------------------------------------------------------------------- /android/app/src/main/res/raw/chime2.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/raw/chime2.m4a -------------------------------------------------------------------------------- /android/app/src/main/res/raw/chime3.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/raw/chime3.m4a -------------------------------------------------------------------------------- /android/app/src/main/res/raw/chime4.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/android/app/src/main/res/raw/chime4.m4a -------------------------------------------------------------------------------- /android/app/src/main/res/raw/keep.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 17 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | 5 | # Versions for our Android build dependencies. 6 | # Defining them here makes them available both in 7 | # settings.gradle and in the build.gradle files. 8 | 9 | agpVersion=8.10.0 10 | 11 | # Generally update this to the version found in recent releases 12 | # of Android Studio, as listed in this table: 13 | # https://kotlinlang.org/docs/releases.html#release-details 14 | # A helpful discussion is at: 15 | # https://stackoverflow.com/a/74425347 16 | kotlinVersion=2.1.20 17 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # How to upgrade Gradle: 2 | # $ tools/gradle wrapper --distribution-type=all --gradle-version=NEW_VERSION 3 | # $ tools/gradle wrapper --distribution-type=all --gradle-version=NEW_VERSION 4 | # (Yep, run the same command twice. The first updates this file so we use 5 | # the new Gradle; the second updates the wrapper's jar and scripts, so that 6 | # the wrapper is the one from the new Gradle too.) 7 | distributionBase=GRADLE_USER_HOME 8 | distributionPath=wrapper/dists 9 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip 10 | networkTimeout=10000 11 | validateDistributionUrl=true 12 | zipStoreBase=GRADLE_USER_HOME 13 | zipStorePath=wrapper/dists 14 | -------------------------------------------------------------------------------- /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 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "$agpVersion" apply false 23 | id "com.android.library" version "$agpVersion" apply false 24 | id "org.jetbrains.kotlin.android" version "$kotlinVersion" apply false 25 | } 26 | 27 | include ':app' 28 | -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Main-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Main-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/KaTeX/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /assets/KaTeX/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2020 Khan Academy and other contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/Noto_Color_Emoji/Noto-COLRv1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/Noto_Color_Emoji/Noto-COLRv1.ttf -------------------------------------------------------------------------------- /assets/Pygments/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-2022 by the respective authors (see AUTHORS file). 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /assets/Source_Code_Pro/SourceCodeVF-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/Source_Code_Pro/SourceCodeVF-Italic.otf -------------------------------------------------------------------------------- /assets/Source_Code_Pro/SourceCodeVF-Upright.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/Source_Code_Pro/SourceCodeVF-Upright.otf -------------------------------------------------------------------------------- /assets/Source_Sans_3/SourceSans3VF-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/Source_Sans_3/SourceSans3VF-Italic.otf -------------------------------------------------------------------------------- /assets/Source_Sans_3/SourceSans3VF-Upright.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/Source_Sans_3/SourceSans3VF-Upright.otf -------------------------------------------------------------------------------- /assets/app-icons/zulip-combined.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/app-icons/zulip-gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/app-icons/zulip-white-z-on-transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/ZulipIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/assets/icons/ZulipIcons.ttf -------------------------------------------------------------------------------- /assets/icons/arrow_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/arrow_left_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/arrow_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/at_sign.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/attach_file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/bot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/check_remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/chevron_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/contacts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/follow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/format_quote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/group_dm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/hash_italic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/hash_sign.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/image.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/inbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/info.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/inherit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/message_checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/icons/mute.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/send.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/share_ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/smile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/icons/star_filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/icons/topic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/topics.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /assets/icons/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/l10n/app_ar.arb: -------------------------------------------------------------------------------- 1 | { 2 | "wildcardMentionAll": "الجميع", 3 | "wildcardMentionEveryone": "الكل", 4 | "wildcardMentionChannel": "القناة", 5 | "wildcardMentionStream": "الدفق", 6 | "wildcardMentionTopic": "الموضوع", 7 | "wildcardMentionChannelDescription": "إخطار القناة", 8 | "wildcardMentionStreamDescription": "إخطار الدفق", 9 | "wildcardMentionAllDmDescription": "إخطار المستلمين", 10 | "wildcardMentionTopicDescription": "إخطار الموضوع" 11 | } 12 | -------------------------------------------------------------------------------- /assets/l10n/app_de.arb: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /assets/l10n/app_en_GB.arb: -------------------------------------------------------------------------------- 1 | { 2 | "topicValidationErrorMandatoryButEmpty": "Topics are required in this organisation.", 3 | "@topicValidationErrorMandatoryButEmpty": { 4 | "description": "Topic validation error when topic is required but was empty." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /assets/l10n/app_ja.arb: -------------------------------------------------------------------------------- 1 | { 2 | "chooseAccountPageTitle": "アカウントを選択", 3 | "@chooseAccountPageTitle": {}, 4 | "chooseAccountButtonAddAnAccount": "新しいアカウントを追加", 5 | "@chooseAccountButtonAddAnAccount": {}, 6 | "profileButtonSendDirectMessage": "ダイレクトメッセージを送信", 7 | "@profileButtonSendDirectMessage": {}, 8 | "userRoleOwner": "オーナー", 9 | "@userRoleOwner": {}, 10 | "userRoleAdministrator": "管理者", 11 | "@userRoleAdministrator": {}, 12 | "userRoleModerator": "モデレータ", 13 | "@userRoleModerator": {}, 14 | "userRoleMember": "メンバー", 15 | "@userRoleMember": {}, 16 | "userRoleGuest": "ゲスト", 17 | "@userRoleGuest": {}, 18 | "userRoleUnknown": "不明", 19 | "@userRoleUnknown": {} 20 | } 21 | -------------------------------------------------------------------------------- /assets/l10n/app_nb.arb: -------------------------------------------------------------------------------- 1 | { 2 | "aboutPageAppVersion": "App versjon", 3 | "@aboutPageAppVersion": { 4 | "description": "Label for Zulip app version in About Zulip page" 5 | }, 6 | "aboutPageTitle": "Om Zulip", 7 | "@aboutPageTitle": { 8 | "description": "Title for About Zulip page." 9 | }, 10 | "aboutPageOpenSourceLicenses": "Lisenser for åpen kildekode", 11 | "@aboutPageOpenSourceLicenses": { 12 | "description": "Item title in About Zulip page to navigate to Licenses page" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /assets/l10n/app_zh.arb: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /assets/l10n/app_zh_Hans_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "settingsPageTitle": "设置" 3 | } 4 | -------------------------------------------------------------------------------- /assets/l10n/app_zh_Hant_TW.arb: -------------------------------------------------------------------------------- 1 | { 2 | "settingsPageTitle": "設定" 3 | } 4 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | source_gen:combining_builder: 5 | options: 6 | ignore_for_file: 7 | - constant_identifier_names 8 | # https://github.com/google/json_serializable.dart/issues/625 9 | - unnecessary_cast 10 | -------------------------------------------------------------------------------- /integration_test/perf_driver.dart: -------------------------------------------------------------------------------- 1 | // This integration driver configures output of timeline data 2 | // and a summary thereof from integration tests. See 3 | // docs/integration_tests.md for background. 4 | 5 | import 'package:flutter_driver/flutter_driver.dart' as driver; 6 | import 'package:integration_test/integration_test_driver.dart'; 7 | 8 | Future main() { 9 | // See cookbook recipe for this sort of driver: 10 | // https://docs.flutter.dev/cookbook/testing/integration/profiling#3-save-the-results-to-disk 11 | return integrationDriver( 12 | responseDataCallback: (data) async { 13 | if (data == null) return; 14 | final timeline = driver.Timeline.fromJson(data['timeline'] as Map); 15 | final summary = driver.TimelineSummary.summarize(timeline); 16 | await summary.writeTimelineToFile( 17 | 'trace_output', 18 | pretty: true, 19 | includeSummary: true); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /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 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | 14.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include "Zulip.xcconfig" 4 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | #include "Zulip.xcconfig" 4 | -------------------------------------------------------------------------------- /ios/Flutter/Zulip.xcconfig: -------------------------------------------------------------------------------- 1 | // Configuration settings file format documentation can be found at: 2 | // https://help.apple.com/xcode/#/dev745c5c974 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # This should match the iOS Deployment Target 2 | # (in Xcode, that's in project > Runner > Info) 3 | # and MinimumOSVersion in ios/Flutter/AppFrameworkInfo.plist. 4 | platform :ios, '14.0' 5 | 6 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 7 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 8 | 9 | project 'Runner', { 10 | 'Debug' => :debug, 11 | 'Profile' => :release, 12 | 'Release' => :release, 13 | } 14 | 15 | def flutter_root 16 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 17 | unless File.exist?(generated_xcode_build_settings_path) 18 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 19 | end 20 | 21 | File.foreach(generated_xcode_build_settings_path) do |line| 22 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 23 | return matches[1].strip if matches 24 | end 25 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 26 | end 27 | 28 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 29 | 30 | flutter_ios_podfile_setup 31 | 32 | target 'Runner' do 33 | use_frameworks! 34 | use_modular_headers! 35 | 36 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.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.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/Runner/Runner.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | # Docs on this config file: 2 | # https://docs.flutter.dev/ui/accessibility-and-localization/internationalization#configuring-the-l10nyaml-file 3 | 4 | synthetic-package: false 5 | arb-dir: assets/l10n 6 | output-dir: lib/generated/l10n 7 | template-arb-file: app_en.arb 8 | required-resource-attributes: true 9 | output-localization-file: zulip_localizations.dart 10 | untranslated-messages-file: build/untranslated_messages.json 11 | output-class: ZulipLocalizations 12 | preferred-supported-locales: [ en ] 13 | nullable-getter: false 14 | -------------------------------------------------------------------------------- /lib/api/model/json.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | /// A value parsed from JSON as either `null` or another value. 4 | /// 5 | /// This can be used to represent JSON properties where absence, null, 6 | /// and some other type are distinguished. 7 | /// For example, with the following field definition: 8 | /// ```dart 9 | /// JsonNullable? name; 10 | /// ``` 11 | /// a `name` value of `null` (as a Dart value) represents 12 | /// the `name` property being absent in JSON; 13 | /// a value of `JsonNullable(null)` represents `'name': null` in JSON; 14 | /// and a value of `JsonNullable("foo")` represents `'name': 'foo'` in JSON. 15 | class JsonNullable { 16 | const JsonNullable(this.value); 17 | 18 | final T? value; 19 | 20 | static JsonNullable? readFromJson( 21 | Map map, String key) { 22 | return map.containsKey(key) ? JsonNullable(map[key] as T?) : null; 23 | } 24 | 25 | @override 26 | bool operator ==(Object other) { 27 | if (other is! JsonNullable) return false; 28 | return value == other.value; 29 | } 30 | 31 | @override 32 | int get hashCode => Object.hash('JsonNullable', value); 33 | } 34 | 35 | class IdentityJsonConverter extends JsonConverter { 36 | const IdentityJsonConverter(); 37 | 38 | @override 39 | T fromJson(T json) => json; 40 | 41 | @override 42 | T toJson(T object) => object; 43 | } 44 | 45 | // Make similar IdentityJsonConverter<…> subclasses as needed. 46 | // Just writing `@IdentityJsonConverter<…>` directly as the annotation 47 | // doesn't work, as json_serializable gets confused. Possibly related: 48 | // https://github.com/google/json_serializable.dart/issues/1398 49 | class NullableStringJsonConverter extends IdentityJsonConverter> { 50 | const NullableStringJsonConverter(); 51 | } 52 | -------------------------------------------------------------------------------- /lib/api/model/narrow.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'narrow.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | const _$IsOperandEnumMap = { 12 | IsOperand.dm: 'dm', 13 | IsOperand.private: 'private', 14 | IsOperand.alerted: 'alerted', 15 | IsOperand.mentioned: 'mentioned', 16 | IsOperand.starred: 'starred', 17 | IsOperand.followed: 'followed', 18 | IsOperand.resolved: 'resolved', 19 | IsOperand.unread: 'unread', 20 | IsOperand.unknown: 'unknown', 21 | }; 22 | -------------------------------------------------------------------------------- /lib/api/model/reaction.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'reaction.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | Reaction _$ReactionFromJson(Map json) => Reaction( 12 | emojiName: json['emoji_name'] as String, 13 | emojiCode: json['emoji_code'] as String, 14 | reactionType: $enumDecode(_$ReactionTypeEnumMap, json['reaction_type']), 15 | userId: (json['user_id'] as num).toInt(), 16 | ); 17 | 18 | Map _$ReactionToJson(Reaction instance) => { 19 | 'emoji_name': instance.emojiName, 20 | 'emoji_code': instance.emojiCode, 21 | 'reaction_type': instance.reactionType, 22 | 'user_id': instance.userId, 23 | }; 24 | 25 | const _$ReactionTypeEnumMap = { 26 | ReactionType.unicodeEmoji: 'unicode_emoji', 27 | ReactionType.realmEmoji: 'realm_emoji', 28 | ReactionType.zulipExtraEmoji: 'zulip_extra_emoji', 29 | }; 30 | -------------------------------------------------------------------------------- /lib/api/route/account.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import '../core.dart'; 4 | 5 | part 'account.g.dart'; 6 | 7 | /// https://zulip.com/api/fetch-api-key 8 | Future fetchApiKey(ApiConnection connection, { 9 | required String username, 10 | required String password, 11 | }) { 12 | return connection.post('fetchApiKey', FetchApiKeyResult.fromJson, 'fetch_api_key', { 13 | 'username': RawParameter(username), 14 | 'password': RawParameter(password), 15 | }); 16 | } 17 | 18 | @JsonSerializable(fieldRename: FieldRename.snake) 19 | class FetchApiKeyResult { 20 | final String apiKey; 21 | final String email; 22 | final int? userId; // TODO(server-7) 23 | 24 | FetchApiKeyResult({ 25 | required this.apiKey, 26 | required this.email, 27 | required this.userId, 28 | }); 29 | 30 | factory FetchApiKeyResult.fromJson(Map json) => 31 | _$FetchApiKeyResultFromJson(json); 32 | 33 | Map toJson() => _$FetchApiKeyResultToJson(this); 34 | } 35 | -------------------------------------------------------------------------------- /lib/api/route/account.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'account.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | FetchApiKeyResult _$FetchApiKeyResultFromJson(Map json) => 12 | FetchApiKeyResult( 13 | apiKey: json['api_key'] as String, 14 | email: json['email'] as String, 15 | userId: (json['user_id'] as num?)?.toInt(), 16 | ); 17 | 18 | Map _$FetchApiKeyResultToJson(FetchApiKeyResult instance) => 19 | { 20 | 'api_key': instance.apiKey, 21 | 'email': instance.email, 22 | 'user_id': instance.userId, 23 | }; 24 | -------------------------------------------------------------------------------- /lib/api/route/channels.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'channels.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | GetStreamTopicsResult _$GetStreamTopicsResultFromJson( 12 | Map json, 13 | ) => GetStreamTopicsResult( 14 | topics: 15 | (json['topics'] as List) 16 | .map((e) => GetStreamTopicsEntry.fromJson(e as Map)) 17 | .toList(), 18 | ); 19 | 20 | Map _$GetStreamTopicsResultToJson( 21 | GetStreamTopicsResult instance, 22 | ) => {'topics': instance.topics}; 23 | 24 | GetStreamTopicsEntry _$GetStreamTopicsEntryFromJson( 25 | Map json, 26 | ) => GetStreamTopicsEntry( 27 | maxId: (json['max_id'] as num).toInt(), 28 | name: TopicName.fromJson(json['name'] as String), 29 | ); 30 | 31 | Map _$GetStreamTopicsEntryToJson( 32 | GetStreamTopicsEntry instance, 33 | ) => {'max_id': instance.maxId, 'name': instance.name}; 34 | -------------------------------------------------------------------------------- /lib/api/route/events.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import '../core.dart'; 4 | import '../model/events.dart'; 5 | import '../model/initial_snapshot.dart'; 6 | 7 | part 'events.g.dart'; 8 | 9 | /// https://zulip.com/api/register-queue 10 | Future registerQueue(ApiConnection connection) { 11 | return connection.post('registerQueue', InitialSnapshot.fromJson, 'register', { 12 | 'apply_markdown': true, 13 | 'slim_presence': true, 14 | 'client_gravatar': false, // TODO(#255): turn on 15 | 'client_capabilities': { 16 | 'notification_settings_null': true, 17 | 'bulk_message_deletion': true, 18 | 'user_avatar_url_field_optional': false, // TODO(#254): turn on 19 | 'stream_typing_notifications': true, 20 | 'user_settings_object': true, 21 | 'empty_topic_name': true, 22 | }, 23 | }); 24 | } 25 | 26 | /// https://zulip.com/api/get-events 27 | Future getEvents(ApiConnection connection, { 28 | required String queueId, int? lastEventId, bool? dontBlock, 29 | }) { 30 | return connection.get('getEvents', GetEventsResult.fromJson, 'events', { 31 | 'queue_id': RawParameter(queueId), 32 | if (lastEventId != null) 'last_event_id': lastEventId, 33 | if (dontBlock != null) 'dont_block': dontBlock, 34 | }); 35 | } 36 | 37 | @JsonSerializable(fieldRename: FieldRename.snake) 38 | class GetEventsResult { 39 | final List events; 40 | // TODO(server): Docs say queueId required; empirically sometimes missing. 41 | final String? queueId; 42 | 43 | GetEventsResult({ 44 | required this.events, 45 | required this.queueId, 46 | }); 47 | 48 | factory GetEventsResult.fromJson(Map json) => 49 | _$GetEventsResultFromJson(json); 50 | 51 | Map toJson() => _$GetEventsResultToJson(this); 52 | } 53 | -------------------------------------------------------------------------------- /lib/api/route/events.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'events.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | GetEventsResult _$GetEventsResultFromJson(Map json) => 12 | GetEventsResult( 13 | events: 14 | (json['events'] as List) 15 | .map((e) => Event.fromJson(e as Map)) 16 | .toList(), 17 | queueId: json['queue_id'] as String?, 18 | ); 19 | 20 | Map _$GetEventsResultToJson(GetEventsResult instance) => 21 | {'events': instance.events, 'queue_id': instance.queueId}; 22 | -------------------------------------------------------------------------------- /lib/api/route/notifications.dart: -------------------------------------------------------------------------------- 1 | import '../core.dart'; 2 | 3 | /// https://zulip.com/api/add-fcm-token 4 | Future addFcmToken(ApiConnection connection, { 5 | required String token, 6 | }) { 7 | return connection.post('addFcmToken', (_) {}, 'users/me/android_gcm_reg_id', { 8 | 'token': RawParameter(token), 9 | }); 10 | } 11 | 12 | /// https://zulip.com/api/remove-fcm-token 13 | Future removeFcmToken(ApiConnection connection, { 14 | required String token, 15 | }) { 16 | return connection.delete('removeFcmToken', (_) {}, 'users/me/android_gcm_reg_id', { 17 | 'token': RawParameter(token), 18 | }); 19 | } 20 | 21 | /// https://zulip.com/api/add-apns-token 22 | Future addApnsToken(ApiConnection connection, { 23 | required String token, 24 | required String appid, 25 | }) { 26 | return connection.post('addApnsToken', (_) {}, 'users/me/apns_device_token', { 27 | 'token': RawParameter(token), 28 | 'appid': RawParameter(appid), 29 | }); 30 | } 31 | 32 | /// https://zulip.com/api/remove-apns-token 33 | Future removeApnsToken(ApiConnection connection, { 34 | required String token, 35 | }) { 36 | return connection.delete('removeApnsToken', (_) {}, 'users/me/apns_device_token', { 37 | 'token': RawParameter(token), 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /lib/api/route/saved_snippets.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import '../core.dart'; 4 | 5 | part 'saved_snippets.g.dart'; 6 | 7 | /// https://zulip.com/api/create-saved-snippet 8 | Future createSavedSnippet(ApiConnection connection, { 9 | required String title, 10 | required String content, 11 | }) { 12 | assert(connection.zulipFeatureLevel! >= 297); // TODO(server-10) 13 | return connection.post('createSavedSnippet', CreateSavedSnippetResult.fromJson, 'saved_snippets', { 14 | 'title': RawParameter(title), 15 | 'content': RawParameter(content), 16 | }); 17 | } 18 | 19 | @JsonSerializable(fieldRename: FieldRename.snake) 20 | class CreateSavedSnippetResult { 21 | final int savedSnippetId; 22 | 23 | CreateSavedSnippetResult({ 24 | required this.savedSnippetId, 25 | }); 26 | 27 | factory CreateSavedSnippetResult.fromJson(Map json) => 28 | _$CreateSavedSnippetResultFromJson(json); 29 | 30 | Map toJson() => _$CreateSavedSnippetResultToJson(this); 31 | } 32 | -------------------------------------------------------------------------------- /lib/api/route/saved_snippets.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'saved_snippets.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | CreateSavedSnippetResult _$CreateSavedSnippetResultFromJson( 12 | Map json, 13 | ) => CreateSavedSnippetResult( 14 | savedSnippetId: (json['saved_snippet_id'] as num).toInt(), 15 | ); 16 | 17 | Map _$CreateSavedSnippetResultToJson( 18 | CreateSavedSnippetResult instance, 19 | ) => {'saved_snippet_id': instance.savedSnippetId}; 20 | -------------------------------------------------------------------------------- /lib/api/route/submessage.dart: -------------------------------------------------------------------------------- 1 | import '../core.dart'; 2 | import '../model/submessage.dart'; 3 | 4 | /// https://zulip.readthedocs.io/en/latest/subsystems/widgets.html#polls-todo-lists-and-games 5 | /// https://github.com/zulip/zulip-mobile/blob/fc23edd67a5ec7f32c7c5f6cd81893b94dc043a2/src/api/submessages/sendSubmessage.js 6 | Future sendSubmessage(ApiConnection connection, { 7 | required int messageId, 8 | required SubmessageType submessageType, 9 | required SubmessageData content, 10 | }) { 11 | return connection.post('sendSubmessage', (_) {}, 'submessage', { 12 | 'message_id': messageId, 13 | 'msg_type': RawParameter(submessageType.toJson()), 14 | 'content': content, 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /lib/api/route/typing.dart: -------------------------------------------------------------------------------- 1 | import '../core.dart'; 2 | import '../model/events.dart'; 3 | import 'messages.dart'; 4 | 5 | 6 | /// https://zulip.com/api/set-typing-status 7 | Future setTypingStatus(ApiConnection connection, { 8 | required TypingOp op, 9 | required MessageDestination destination, 10 | }) { 11 | switch (destination) { 12 | case StreamDestination(): 13 | final supportsTypeChannel = connection.zulipFeatureLevel! >= 248; // TODO(server-9) 14 | final supportsStreamId = connection.zulipFeatureLevel! >= 215; // TODO(server-8) 15 | return connection.post('setTypingStatus', (_) {}, 'typing', { 16 | 'op': RawParameter(op.toJson()), 17 | 'type': RawParameter(supportsTypeChannel ? 'channel' : 'stream'), 18 | if (supportsStreamId) 'stream_id': destination.streamId 19 | else 'to': [destination.streamId], 20 | 'topic': RawParameter(destination.topic.apiName), 21 | }); 22 | case DmDestination(): 23 | final supportsDirect = connection.zulipFeatureLevel! >= 174; // TODO(server-7) 24 | return connection.post('setTypingStatus', (_) {}, 'typing', { 25 | 'op': RawParameter(op.toJson()), 26 | 'type': RawParameter(supportsDirect ? 'direct' : 'private'), 27 | 'to': destination.userIds, 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/api/route/users.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | import '../core.dart'; 4 | 5 | part 'users.g.dart'; 6 | 7 | /// https://zulip.com/api/get-own-user, abridged 8 | /// 9 | /// This route's return type is simplified because we use it only 10 | /// as a workaround on old servers. 11 | Future getOwnUser(ApiConnection connection) { 12 | return connection.get('getOwnUser', GetOwnUserResult.fromJson, 'users/me', {}); 13 | } 14 | 15 | @JsonSerializable(fieldRename: FieldRename.snake) 16 | class GetOwnUserResult { 17 | final int userId; 18 | 19 | // There are many more properties in this route's result. 20 | // But we use this route only as a workaround on old servers: 21 | // https://github.com/zulip/zulip/issues/24980 22 | // https://chat.zulip.org/#narrow/stream/378-api-design/topic/user.20ID.20in.20fetch-api-key/near/1540592 23 | // for which `userId` is the only property we need. 24 | // TODO(server-7): Drop getOwnUser entirely, relying on userId from fetchApiKey. 25 | 26 | GetOwnUserResult({ 27 | required this.userId, 28 | }); 29 | 30 | factory GetOwnUserResult.fromJson(Map json) => 31 | _$GetOwnUserResultFromJson(json); 32 | 33 | Map toJson() => _$GetOwnUserResultToJson(this); 34 | } 35 | -------------------------------------------------------------------------------- /lib/api/route/users.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'users.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | GetOwnUserResult _$GetOwnUserResultFromJson(Map json) => 12 | GetOwnUserResult(userId: (json['user_id'] as num).toInt()); 13 | 14 | Map _$GetOwnUserResultToJson(GetOwnUserResult instance) => 15 | {'user_id': instance.userId}; 16 | -------------------------------------------------------------------------------- /lib/licenses.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | /// Our [LicenseEntryCollector] for licenses that aren't included by default. 5 | /// 6 | /// Licenses that ship with our Dart-package dependencies are included 7 | /// automatically. This collects other licenses, such as for fonts we include in 8 | /// our asset bundle. 9 | // If the license text is meant to be read from a file in the asset bundle, 10 | // remember to include the file in the asset bundle by listing its path 11 | // under `assets` in pubspec.yaml. 12 | Stream additionalLicenses() async* { 13 | // Alphabetic by path. 14 | 15 | yield LicenseEntryWithLineBreaks( 16 | ['KaTeX'], 17 | await rootBundle.loadString('assets/KaTeX/LICENSE')); 18 | yield LicenseEntryWithLineBreaks( 19 | ['Noto Color Emoji'], 20 | await rootBundle.loadString('assets/Noto_Color_Emoji/LICENSE')); 21 | yield LicenseEntryWithLineBreaks( 22 | ['Pygments'], 23 | await () async { 24 | final [licenseFileText, authorsFileText] = await Future.wait([ 25 | rootBundle.loadString('assets/Pygments/LICENSE.txt'), 26 | rootBundle.loadString('assets/Pygments/AUTHORS.txt'), 27 | ]); 28 | 29 | // This does not need to be translated, as it is just a small fragment 30 | // of text surrounded by a large quantity of English text that isn't 31 | // translated anyway. 32 | // (And it would be logistically tricky to translate, as this code is 33 | // called from the `main` function before the [ZulipApp] widget is built, 34 | // let alone has updated [GlobalLocalizations].) 35 | return '$licenseFileText\n\nAUTHORS file follows:\n\n$authorsFileText'; 36 | }()); 37 | yield LicenseEntryWithLineBreaks( 38 | ['Source Code Pro'], 39 | await rootBundle.loadString('assets/Source_Code_Pro/LICENSE.md')); 40 | yield LicenseEntryWithLineBreaks( 41 | ['Source Sans 3'], 42 | await rootBundle.loadString('assets/Source_Sans_3/LICENSE.md')); 43 | 44 | // Alphabetic by path. 45 | } 46 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import 'licenses.dart'; 5 | import 'log.dart'; 6 | import 'model/binding.dart'; 7 | import 'notifications/receive.dart'; 8 | import 'widgets/app.dart'; 9 | 10 | void main() { 11 | assert(() { 12 | debugLogEnabled = true; 13 | return true; 14 | }()); 15 | LicenseRegistry.addLicense(additionalLicenses); 16 | WidgetsFlutterBinding.ensureInitialized(); 17 | LiveZulipBinding.ensureInitialized(); 18 | NotificationService.instance.start(); 19 | runApp(const ZulipApp()); 20 | } 21 | -------------------------------------------------------------------------------- /lib/model/actions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | import '../notifications/display.dart'; 6 | import '../notifications/receive.dart'; 7 | import 'store.dart'; 8 | 9 | // TODO: Make this a part of GlobalStore 10 | Future logOutAccount(GlobalStore globalStore, int accountId) async { 11 | final account = globalStore.getAccount(accountId); 12 | if (account == null) return; // TODO(log) 13 | 14 | // Unawaited, to not block removing the account on this request. 15 | unawaited(unregisterToken(globalStore, accountId)); 16 | 17 | if (defaultTargetPlatform == TargetPlatform.android) { 18 | unawaited(NotificationDisplayManager.removeNotificationsForAccount(account.realmUrl, account.userId)); 19 | } 20 | 21 | await globalStore.removeAccount(accountId); 22 | } 23 | 24 | Future unregisterToken(GlobalStore globalStore, int accountId) async { 25 | final account = globalStore.getAccount(accountId); 26 | if (account == null) return; // TODO(log) 27 | 28 | // TODO(#322) use actual acked push token; until #322, this is just null. 29 | final token = account.ackedPushToken 30 | // Try the current token as a fallback; maybe the server has registered 31 | // it and we just haven't recorded that fact in the client. 32 | ?? NotificationService.instance.token.value; 33 | if (token == null) return; 34 | 35 | final connection = globalStore.apiConnectionFromAccount(account); 36 | try { 37 | await NotificationService.unregisterToken(connection, token: token); 38 | } catch (e) { 39 | // TODO retry? handle failures? 40 | } finally { 41 | connection.close(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/model/avatar_url.dart: -------------------------------------------------------------------------------- 1 | /// The size threshold above which is "medium" size for an avatar. 2 | /// 3 | /// This is DEFAULT_AVATAR_SIZE in zerver/lib/upload.py. 4 | const defaultUploadSizePx = 100; 5 | 6 | abstract class AvatarUrl { 7 | factory AvatarUrl.fromUserData({required Uri resolvedUrl}) { 8 | // TODO(#255): handle computing gravatars 9 | // TODO(#254): handle computing fallback avatar 10 | if (resolvedUrl.toString().startsWith(GravatarUrl.origin)) { 11 | return GravatarUrl(resolvedUrl: resolvedUrl); 12 | } else { 13 | return UploadedAvatarUrl(resolvedUrl: resolvedUrl); 14 | } 15 | } 16 | 17 | Uri get(int sizePhysicalPx); 18 | } 19 | 20 | class GravatarUrl implements AvatarUrl { 21 | GravatarUrl({required Uri resolvedUrl}) : standardUrl = resolvedUrl; 22 | 23 | static String origin = 'https://secure.gravatar.com'; 24 | 25 | Uri standardUrl; 26 | 27 | @override 28 | Uri get(int sizePhysicalPx) { 29 | return standardUrl.replace(queryParameters: { 30 | ...standardUrl.queryParameters, 31 | 's': sizePhysicalPx.toString(), 32 | }); 33 | } 34 | } 35 | 36 | class UploadedAvatarUrl implements AvatarUrl { 37 | UploadedAvatarUrl({required Uri resolvedUrl}) : standardUrl = resolvedUrl; 38 | 39 | Uri standardUrl; 40 | 41 | @override 42 | Uri get(int sizePhysicalPx) { 43 | if (sizePhysicalPx > defaultUploadSizePx) { 44 | return standardUrl.replace( 45 | path: standardUrl.path.replaceFirst(RegExp(r'(?:\.png)?$'), "-medium.png")); 46 | } 47 | 48 | return standardUrl; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/model/internal_link.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | // ignore_for_file: constant_identifier_names, unnecessary_cast 4 | 5 | part of 'internal_link.dart'; 6 | 7 | // ************************************************************************** 8 | // JsonSerializableGenerator 9 | // ************************************************************************** 10 | 11 | const _$_NarrowOperatorEnumMap = { 12 | _NarrowOperator.dm: 'dm', 13 | _NarrowOperator.near: 'near', 14 | _NarrowOperator.with_: 'with', 15 | _NarrowOperator.is_: 'is', 16 | _NarrowOperator.pmWith: 'pm-with', 17 | _NarrowOperator.stream: 'stream', 18 | _NarrowOperator.channel: 'channel', 19 | _NarrowOperator.subject: 'subject', 20 | _NarrowOperator.topic: 'topic', 21 | _NarrowOperator.unknown: 'unknown', 22 | }; 23 | -------------------------------------------------------------------------------- /lib/model/localizations.dart: -------------------------------------------------------------------------------- 1 | import '../generated/l10n/zulip_localizations.dart'; 2 | 3 | abstract final class GlobalLocalizations { 4 | /// The [ZulipLocalizations] for the user's chosen language and locale. 5 | /// 6 | /// Where possible, the [ZulipLocalizations] should be acquired 7 | /// through [ZulipLocalizations.of] instead, using a [BuildContext]. 8 | /// This static field is to be used where access to a [BuildContext] 9 | /// is impractical, such as in the API bindings 10 | /// (which use localizations when throwing exceptions). 11 | /// 12 | /// This gets set during app startup once we have the user's choice of locale. 13 | /// If accessed before that point, it uses the app's first supported locale, 14 | /// namely 'en'. 15 | static ZulipLocalizations zulipLocalizations = 16 | lookupZulipLocalizations(ZulipLocalizations.supportedLocales.first); 17 | } 18 | -------------------------------------------------------------------------------- /lib/model/saved_snippet.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | import '../api/model/events.dart'; 4 | import '../api/model/model.dart'; 5 | import 'store.dart'; 6 | 7 | mixin SavedSnippetStore { 8 | Map get savedSnippets; 9 | } 10 | 11 | class SavedSnippetStoreImpl extends PerAccountStoreBase with SavedSnippetStore { 12 | SavedSnippetStoreImpl({ 13 | required super.core, 14 | required Iterable savedSnippets, 15 | }) : _savedSnippets = { 16 | for (final savedSnippet in savedSnippets) 17 | savedSnippet.id: savedSnippet, 18 | }; 19 | 20 | @override 21 | late Map savedSnippets = UnmodifiableMapView(_savedSnippets); 22 | final Map _savedSnippets; 23 | 24 | void handleSavedSnippetsEvent(SavedSnippetsEvent event) { 25 | switch (event) { 26 | case SavedSnippetsAddEvent(:final savedSnippet): 27 | _savedSnippets[savedSnippet.id] = savedSnippet; 28 | 29 | case SavedSnippetsUpdateEvent(:final savedSnippet): 30 | assert(_savedSnippets[savedSnippet.id]!.dateCreated 31 | == savedSnippet.dateCreated); // TODO(log) 32 | _savedSnippets[savedSnippet.id] = savedSnippet; 33 | 34 | case SavedSnippetsRemoveEvent(:final savedSnippetId): 35 | _savedSnippets.remove(savedSnippetId); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/widgets/input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// A space to use for [InputDecoration.helperText] so the layout doesn't jump. 4 | /// 5 | /// In particular, U+200B ZERO WIDTH SPACE. 6 | /// 7 | /// See [FormField.validator] : 8 | /// > Alternating between error and normal state can cause the height of the 9 | /// > [TextFormField] to change if no other subtext decoration is set on the 10 | /// > field. To create a field whose height is fixed regardless of whether or 11 | /// > not an error is displayed, either wrap the [TextFormField] in a fixed 12 | /// > height parent like [SizedBox], or set the [InputDecoration.helperText] 13 | /// > parameter to a space. 14 | // TODO(upstream?): This contentless `helperText` shouldn't get its own node 15 | // in the semantics tree. Empirically, it does: iOS VoiceOver's focus can land 16 | // on it, and when it does, nothing gets read. 17 | const String kLayoutPinningHelperText = '\u200b'; 18 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 16 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 17 | g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); 19 | sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); 20 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 22 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | sqlite3_flutter_libs 8 | url_launcher_linux 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | ) 13 | 14 | set(PLUGIN_BUNDLED_LIBRARIES) 15 | 16 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 21 | endforeach(plugin) 22 | 23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 26 | endforeach(ffi_plugin) 27 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import app_settings 9 | import device_info_plus 10 | import file_picker 11 | import file_selector_macos 12 | import firebase_core 13 | import firebase_messaging 14 | import package_info_plus 15 | import path_provider_foundation 16 | import share_plus 17 | import sqlite3_flutter_libs 18 | import url_launcher_macos 19 | import video_player_avfoundation 20 | import wakelock_plus 21 | 22 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 23 | AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin")) 24 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 25 | FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) 26 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 27 | FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) 28 | FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) 29 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 30 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 31 | SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) 32 | Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) 33 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 34 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 35 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 36 | } 37 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | # This should match the macOS Deployment Target 2 | # (in Xcode, that's in project > Runner > Info) 3 | platform :osx, '14.5' 4 | 5 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 6 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 7 | 8 | project 'Runner', { 9 | 'Debug' => :debug, 10 | 'Profile' => :release, 11 | 'Release' => :release, 12 | } 13 | 14 | def flutter_root 15 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 16 | unless File.exist?(generated_xcode_build_settings_path) 17 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 18 | end 19 | 20 | File.foreach(generated_xcode_build_settings_path) do |line| 21 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 22 | return matches[1].strip if matches 23 | end 24 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 25 | end 26 | 27 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 28 | 29 | flutter_macos_podfile_setup 30 | 31 | target 'Runner' do 32 | use_frameworks! 33 | use_modular_headers! 34 | 35 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 36 | end 37 | 38 | post_install do |installer| 39 | installer.pods_project.targets.each do |target| 40 | flutter_additional_macos_build_settings(target) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = Zulip 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.zulip.flutter 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 Kandra Labs, Inc., and contributors. Licensed under the Apache License, Version 2.0. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/zulip_plugin/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "31209d04ffdb575ab5e2bd587eb606c0faed3e04" 8 | channel: "main" 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 31209d04ffdb575ab5e2bd587eb606c0faed3e04 17 | base_revision: 31209d04ffdb575ab5e2bd587eb606c0faed3e04 18 | - platform: android 19 | create_revision: 31209d04ffdb575ab5e2bd587eb606c0faed3e04 20 | base_revision: 31209d04ffdb575ab5e2bd587eb606c0faed3e04 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 | -------------------------------------------------------------------------------- /packages/zulip_plugin/android/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | namespace "com.zulip.flutter" 8 | 9 | // This Gradle project is an empty placeholder that contains no actual code, 10 | // so the settings below have no real effect. The Gradle project exists only 11 | // because the Flutter Gradle plugin expects every Flutter plugin to have one. 12 | 13 | compileSdk 34 14 | 15 | compileOptions { 16 | sourceCompatibility = JavaVersion.VERSION_1_8 17 | targetCompatibility = JavaVersion.VERSION_1_8 18 | } 19 | 20 | kotlinOptions { 21 | jvmTarget = "1.8" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/zulip_plugin/android/src/main/kotlin/com/zulip/flutter/ZulipShimPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.zulip.flutter 2 | 3 | import io.flutter.embedding.engine.plugins.FlutterPlugin 4 | import java.lang.reflect.Constructor 5 | 6 | /** 7 | * A Flutter plugin that just wraps ZulipPlugin. 8 | * 9 | * For background, see comment in the `pubspec.yaml` file 10 | * of this `zulip_plugin` package. 11 | */ 12 | class ZulipShimPlugin : FlutterPlugin { 13 | private val plugin: FlutterPlugin = pluginConstructor.newInstance() 14 | 15 | override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { 16 | plugin.onAttachedToEngine(binding) 17 | } 18 | 19 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 20 | plugin.onDetachedFromEngine(binding) 21 | } 22 | 23 | companion object { 24 | private val pluginConstructor: Constructor by lazy { 25 | // We can't give this shim a compile-time dependency on ZulipPlugin 26 | // because that lives in the main application package, which depends on 27 | // this `zulip_plugin` package. 28 | // 29 | // (The root cause here is that the purpose of this shim plugin is to be 30 | // visible to the `flutter` tool so that the tool includes it in 31 | // GeneratedPluginRegistrant, and the structure the `flutter` tool 32 | // expects plugins to have is that the plugin corresponds to a package 33 | // which the application depends on.) 34 | // 35 | // Lacking a compile-time dependency, we instead use reflection 36 | // to find ZulipPlugin. 37 | val pluginClass = Class.forName("com.zulip.flutter.ZulipPlugin") 38 | @Suppress("UNCHECKED_CAST") 39 | (pluginClass as Class).getConstructor() 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | 2 | { pkgs ? import {} }: 3 | with pkgs; 4 | mkShell { 5 | 6 | nativeBuildInputs = [ 7 | clang 8 | cmake 9 | ninja 10 | pkg-config 11 | 12 | gtk3 # Curiously `nix-env -i` can't handle this one adequately. 13 | # But `nix-shell` on this shell.nix does fine. 14 | pcre 15 | libepoxy 16 | 17 | # This group all seem not strictly necessary -- commands like 18 | # `flutter run -d linux` seem to *work* fine without them, but 19 | # the build does print messages about missing packages, like: 20 | # Package mount was not found in the pkg-config search path. 21 | # Perhaps you should add the directory containing `mount.pc' 22 | # to the PKG_CONFIG_PATH environment variable 23 | # To add to this list on NixOS upgrades, the Nix package 24 | # `nix-index` is handy: then `nix-locate mount.pc`. 25 | libuuid # for mount.pc 26 | xorg.libXdmcp.dev 27 | python310Packages.libselinux.dev # for libselinux.pc 28 | libsepol.dev 29 | libthai.dev 30 | libdatrie.dev 31 | libxkbcommon.dev 32 | dbus.dev 33 | at-spi2-core.dev 34 | xorg.libXtst.out 35 | pcre2.dev 36 | 37 | jdk17 38 | android-studio 39 | android-tools 40 | 41 | nodejs 42 | ]; 43 | 44 | LD_LIBRARY_PATH = lib.makeLibraryPath [ 45 | fontconfig.lib 46 | sqlite.out 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /test/api/core_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/api/core.dart'; 3 | 4 | extension ApiConnectionChecks on Subject { 5 | Subject get realmUrl => has((x) => x.realmUrl, 'realmUrl'); 6 | Subject get zulipFeatureLevel => has((x) => x.zulipFeatureLevel, 'zulipFeatureLevel'); 7 | } 8 | -------------------------------------------------------------------------------- /test/api/exception_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/api/exception.dart'; 3 | 4 | extension ApiRequestExceptionChecks on Subject { 5 | Subject get routeName => has((e) => e.routeName, 'routeName'); 6 | Subject get message => has((e) => e.message, 'message'); 7 | Subject get asString => has((u) => u.toString(), 'toString'); // TODO(checks): what's a good convention for this? 8 | } 9 | 10 | extension ZulipApiExceptionChecks on Subject { 11 | Subject get code => has((e) => e.code, 'code'); 12 | Subject get httpStatus => has((e) => e.httpStatus, 'httpStatus'); 13 | Subject> get data => has((e) => e.data, 'data'); 14 | } 15 | 16 | extension NetworkExceptionChecks on Subject { 17 | Subject get cause => has((e) => e.cause, 'cause'); 18 | } 19 | 20 | extension ServerExceptionChecks on Subject { 21 | Subject get httpStatus => has((e) => e.httpStatus, 'httpStatus'); 22 | Subject?> get data => has((e) => e.data, 'data'); 23 | } 24 | 25 | extension Server5xxExceptionChecks on Subject { 26 | // no properties not on ServerException 27 | } 28 | 29 | extension MalformedServerResponseExceptionChecks on Subject { 30 | Subject get causeException => has((e) => e.causeException, 'causeException'); 31 | } 32 | -------------------------------------------------------------------------------- /test/api/exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:test/scaffolding.dart'; 3 | import 'package:zulip/api/exception.dart'; 4 | 5 | void main() { 6 | test('ZulipApiException.toString', () { 7 | void checkValue(String expected, 8 | {required int httpStatus, required String code, 9 | required Map data}) { 10 | check(ZulipApiException( 11 | code: code, httpStatus: httpStatus, data: data, 12 | routeName: 'aRoute', message: 'oops', 13 | ).toString()).equals('ZulipApiException:$expected aRoute: oops'); 14 | } 15 | 16 | checkValue(httpStatus: 400, code: 'BAD_REQUEST', data: {}, ''); 17 | checkValue(httpStatus: 401, code: 'BAD_REQUEST', data: {}, ' 401'); 18 | checkValue(httpStatus: 400, code: 'PROBLEM', data: {}, ' PROBLEM'); 19 | checkValue(httpStatus: 400, code: 'BAD_REQUEST', data: {'x': 'y'}, ' {"x":"y"}'); 20 | 21 | check(ZulipApiException( 22 | httpStatus: 401, code: 'PROBLEM', data: {'x': 'y'}, 23 | routeName: 'aRoute', message: 'oops').toString() 24 | ).equals('ZulipApiException: 401 PROBLEM {"x":"y"} aRoute: oops'); 25 | }); 26 | 27 | // NetworkException.toString: see "API network errors" test in core_test.dart 28 | } 29 | -------------------------------------------------------------------------------- /test/api/model/json_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:test/scaffolding.dart'; 3 | import 'package:zulip/api/model/json.dart'; 4 | 5 | void main() { 6 | group('JsonNullable', () { 7 | test('value', () { 8 | check(const JsonNullable(null)).value.isNull(); 9 | check(const JsonNullable(3)).value.equals(3); 10 | check(const JsonNullable(JsonNullable(3))).value 11 | ..identicalTo(const JsonNullable(3)) 12 | ..isNotNull().value.equals(3); 13 | }); 14 | 15 | test('readFromJson', () { 16 | check(JsonNullable.readFromJson({}, 'a')).isNull(); 17 | check(JsonNullable.readFromJson({'a': null}, 'a')).equals(const JsonNullable(null)); 18 | check(JsonNullable.readFromJson({'a': 3}, 'a')).equals(const JsonNullable(3)); 19 | }); 20 | 21 | test('==/hashCode', () { 22 | // ignore: prefer_const_constructors 23 | check(JsonNullable(null)).equals(JsonNullable(null)); 24 | // ignore: prefer_const_constructors 25 | check(JsonNullable(3)).equals(JsonNullable(3)); 26 | // ignore: prefer_const_constructors 27 | check(JsonNullable(JsonNullable(3))).equals(JsonNullable(JsonNullable(3))); 28 | 29 | const values = [JsonNullable(null), JsonNullable(3), JsonNullable(4), 30 | JsonNullable(JsonNullable(null)), JsonNullable(JsonNullable(3))]; 31 | for (int i = 0; i < values.length; i++) { 32 | for (int j = i + 1; j < values.length; j++) { 33 | check(values[i]).not((it) => it.equals(values[j])); 34 | check(values[i].hashCode).not((it) => it.equals(values[j].hashCode)); 35 | } 36 | } 37 | }); 38 | }); 39 | } 40 | 41 | extension JsonNullableChecks on Subject> { 42 | Subject get value => has((x) => x.value, 'value'); 43 | } 44 | -------------------------------------------------------------------------------- /test/api/model/narrow_test.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | // resolveApiNarrowForServer is covered in test/api/route/messages_test.dart, 3 | // in the ApiNarrow.toJson test. 4 | } 5 | -------------------------------------------------------------------------------- /test/api/model/submessage_checks.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:checks/checks.dart'; 3 | import 'package:zulip/api/model/submessage.dart'; 4 | 5 | extension SubmessageChecks on Subject { 6 | Subject get senderId => has((e) => e.senderId, 'senderId'); 7 | Subject get msgType => has((e) => e.msgType, 'msgType'); 8 | Subject get content => has((e) => e.content, 'content'); 9 | } 10 | 11 | extension WidgetDataChecks on Subject { 12 | Subject get widgetType => has((e) => e.widgetType, 'widgetType'); 13 | } 14 | 15 | extension PollWidgetDataChecks on Subject { 16 | Subject get extraData => has((e) => e.extraData, 'extraData'); 17 | } 18 | 19 | extension PollWidgetExtraDataChecks on Subject { 20 | Subject get question => has((e) => e.question, 'question'); 21 | Subject> get options => has((e) => e.options, 'options'); 22 | } 23 | 24 | extension PollEventChecks on Subject { 25 | Subject get type => has((e) => e.type, 'type'); 26 | } 27 | 28 | extension PollOptionEventChecks on Subject { 29 | Subject get option => has((e) => e.option, 'option'); 30 | } 31 | 32 | extension PollQuestionEventChecks on Subject { 33 | Subject get question => has((e) => e.question, 'question'); 34 | } 35 | 36 | extension PollVoteEventChecks on Subject { 37 | Subject get key => has((e) => e.key, 'key'); 38 | Subject get op => has((e) => e.op, 'op'); 39 | } 40 | 41 | extension PollChecks on Subject { 42 | Subject get question => has((e) => e.question, 'question'); 43 | Subject> get options => has((e) => e.options, 'options'); 44 | } 45 | 46 | extension PollOptionChecks on Subject { 47 | Subject get text => has((e) => e.text, 'text'); 48 | Subject> get voters => has((e) => e.voters, 'voters'); 49 | } 50 | -------------------------------------------------------------------------------- /test/api/route/realm_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'package:test/scaffolding.dart'; 4 | import 'package:zulip/api/core.dart'; 5 | import 'package:zulip/api/route/realm.dart'; 6 | 7 | import '../../stdlib_checks.dart'; 8 | import '../fake_api.dart'; 9 | 10 | void main() { 11 | group('fetchServerEmojiData', () { 12 | Future checkFetchServerEmojiData(FakeApiConnection connection, { 13 | required Uri emojiDataUrl, 14 | }) async { 15 | final result = await fetchServerEmojiData(connection, 16 | emojiDataUrl: emojiDataUrl); 17 | check(connection.lastRequest).isA() 18 | ..method.equals('GET') 19 | ..url.equals(emojiDataUrl) 20 | ..headers.deepEquals(kFallbackUserAgentHeader); 21 | return result; 22 | } 23 | 24 | final fakeResult = ServerEmojiData(codeToNames: { 25 | '1f642': ['slight_smile'], 26 | '1f34a': ['orange', 'tangerine', 'mandarin'], 27 | }); 28 | 29 | test('smoke', () { 30 | return FakeApiConnection.with_((connection) async { 31 | connection.prepare(json: fakeResult.toJson()); 32 | check(await checkFetchServerEmojiData(connection, 33 | emojiDataUrl: connection.realmUrl.resolve('/static/emoji.json') 34 | )).jsonEquals(fakeResult); 35 | }); 36 | }); 37 | 38 | test('off-realm is OK', () { 39 | return FakeApiConnection.with_( 40 | realmUrl: Uri.parse('https://chat.example'), (connection) async { 41 | connection.prepare(json: fakeResult.toJson()); 42 | check(await checkFetchServerEmojiData(connection, 43 | emojiDataUrl: Uri.parse('https://elsewhere.example/static/emoji.json') 44 | )).jsonEquals(fakeResult); 45 | }); 46 | }); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /test/api/route/route_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/api/route/messages.dart'; 3 | import 'package:zulip/api/route/saved_snippets.dart'; 4 | 5 | extension SendMessageResultChecks on Subject { 6 | Subject get id => has((e) => e.id, 'id'); 7 | } 8 | extension CreateSavedSnippetResultChecks on Subject { 9 | Subject get savedSnippetId => has((e) => e.savedSnippetId, 'savedSnippetId'); 10 | } 11 | 12 | // TODO add similar extensions for other classes in api/route/*.dart 13 | -------------------------------------------------------------------------------- /test/api/route/saved_snippets_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:zulip/api/route/saved_snippets.dart'; 5 | 6 | import '../../stdlib_checks.dart'; 7 | import '../fake_api.dart'; 8 | import 'route_checks.dart'; 9 | 10 | void main() { 11 | test('smoke', () async { 12 | return FakeApiConnection.with_((connection) async { 13 | connection.prepare( 14 | json: CreateSavedSnippetResult(savedSnippetId: 123).toJson()); 15 | final result = await createSavedSnippet(connection, 16 | title: 'test saved snippet', content: 'content'); 17 | check(connection.takeRequests()).single.isA() 18 | ..method.equals('POST') 19 | ..url.path.equals('/api/v1/saved_snippets') 20 | ..bodyFields.deepEquals({ 21 | 'title': 'test saved snippet', 22 | 'content': 'content', 23 | }); 24 | check(result).savedSnippetId.equals(123); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/api/route/submessage_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:http/http.dart' as http; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:zulip/api/model/submessage.dart'; 5 | import 'package:zulip/api/route/submessage.dart'; 6 | 7 | import '../../stdlib_checks.dart'; 8 | import '../fake_api.dart'; 9 | 10 | void main() { 11 | test('smoke sendSubmessage', () { 12 | return FakeApiConnection.with_((connection) async { 13 | connection.prepare(json: {}); 14 | await sendSubmessage(connection, messageId: 1, 15 | submessageType: SubmessageType.widget, 16 | content: PollQuestionEventSubmessage(question: 'test question')); 17 | check(connection.takeRequests()).single.isA() 18 | ..method.equals('POST') 19 | ..url.path.equals('/api/v1/submessage') 20 | ..bodyFields.deepEquals({ 21 | 'message_id': '1', 22 | 'msg_type': 'widget', 23 | 'content': '{"type":"question","question":"test question"}', 24 | }); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /test/fake_async_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:fake_async/fake_async.dart'; 3 | 4 | extension FakeTimerChecks on Subject { 5 | Subject get duration => has((t) => t.duration, 'duration'); 6 | } 7 | -------------------------------------------------------------------------------- /test/fake_async_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:checks/checks.dart'; 4 | import 'package:clock/clock.dart'; 5 | import 'package:test/scaffolding.dart'; 6 | 7 | import 'fake_async.dart'; 8 | 9 | void main() { 10 | group('awaitFakeAsync', () { 11 | test('basic success', () { 12 | const duration = Duration(milliseconds: 100); 13 | check(awaitFakeAsync((async) async { 14 | final start = clock.now(); 15 | await Future.delayed(duration); 16 | return clock.now().difference(start); 17 | })).equals(duration); 18 | }); 19 | 20 | int someFunction() => throw _TestException(); 21 | 22 | test('propagates error from future returned by callback', () { 23 | try { 24 | awaitFakeAsync((async) async => someFunction()); 25 | assert(false); 26 | } catch (e, s) { 27 | check(e).isA<_TestException>(); 28 | check(s.toString()).contains('someFunction'); 29 | } 30 | }); 31 | 32 | test('propagates error from callback itself', () { 33 | try { 34 | awaitFakeAsync((async) => Future.value(someFunction())); 35 | assert(false); 36 | } catch (e, s) { 37 | check(e).isA<_TestException>(); 38 | check(s.toString()).contains('someFunction'); 39 | } 40 | }); 41 | 42 | test('TimeoutException on deadlocked callback', () { 43 | check(() => awaitFakeAsync((async) async { 44 | await Completer().future; 45 | })).throws(); 46 | }); 47 | }); 48 | } 49 | 50 | class _TestException {} 51 | -------------------------------------------------------------------------------- /test/flutter_test_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | Future testExecutable(FutureOr Function() testMain) async { 6 | // Enable these checks. See this property's doc, which is basically 7 | // an apology for the fact that (for historical reasons) it isn't 8 | // the default. 9 | WidgetController.hitTestWarningShouldBeFatal = true; 10 | 11 | await testMain(); 12 | } 13 | -------------------------------------------------------------------------------- /test/model/autocomplete_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/api/model/model.dart'; 3 | import 'package:zulip/model/autocomplete.dart'; 4 | import 'package:zulip/widgets/compose_box.dart'; 5 | 6 | extension ComposeContentControllerChecks on Subject { 7 | Subject?> get autocompleteIntent => has((c) => c.autocompleteIntent(), 'autocompleteIntent'); 8 | } 9 | 10 | extension ComposeTopicControllerChecks on Subject { 11 | Subject?> get autocompleteIntent => has((c) => c.autocompleteIntent(), 'autocompleteIntent'); 12 | } 13 | 14 | extension AutocompleteIntentChecks on Subject> { 15 | Subject get syntaxStart => has((i) => i.syntaxStart, 'syntaxStart'); 16 | Subject get query => has((i) => i.query, 'query'); 17 | } 18 | 19 | extension UserMentionAutocompleteResultChecks on Subject { 20 | Subject get userId => has((r) => r.userId, 'userId'); 21 | } 22 | 23 | extension TopicAutocompleteResultChecks on Subject { 24 | Subject get topic => has((r) => r.topic, 'topic'); 25 | } 26 | -------------------------------------------------------------------------------- /test/model/avatar_url_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:test/scaffolding.dart'; 3 | import 'package:zulip/model/avatar_url.dart'; 4 | 5 | void main() { 6 | const defaultSize = 30; 7 | const largeSize = 120; 8 | 9 | group('GravatarUrl', () { 10 | test('gravatar url', () { 11 | final url = '${GravatarUrl.origin}/avatar/1234'; 12 | final avatarUrl = AvatarUrl.fromUserData(resolvedUrl: Uri.parse(url)); 13 | 14 | check(avatarUrl.get(defaultSize).toString()).equals('$url?s=30'); 15 | }); 16 | }); 17 | 18 | group('UploadedAvatarUrl', () { 19 | test('png image', () { 20 | const url = 'https://zulip.example/image.png'; 21 | final avatarUrl = AvatarUrl.fromUserData(resolvedUrl: Uri.parse(url)); 22 | 23 | check(avatarUrl.get(defaultSize).toString()).equals(url); 24 | }); 25 | 26 | test('png image, larger size', () { 27 | const url = 'https://zulip.example/image.png'; 28 | final avatarUrl = AvatarUrl.fromUserData(resolvedUrl: Uri.parse(url)); 29 | final expectedUrl = url.replaceAll('.png', '-medium.png'); 30 | 31 | check(avatarUrl.get(largeSize).toString()).equals(expectedUrl); 32 | }); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /test/model/message_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/api/model/model.dart'; 3 | import 'package:zulip/model/message.dart'; 4 | 5 | extension OutboxMessageChecks on Subject> { 6 | Subject get localMessageId => has((x) => x.localMessageId, 'localMessageId'); 7 | Subject get state => has((x) => x.state, 'state'); 8 | Subject get hidden => has((x) => x.hidden, 'hidden'); 9 | } 10 | -------------------------------------------------------------------------------- /test/model/narrow_checks.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:checks/checks.dart'; 3 | import 'package:zulip/api/model/model.dart'; 4 | import 'package:zulip/api/model/narrow.dart'; 5 | import 'package:zulip/model/narrow.dart'; 6 | 7 | extension NarrowChecks on Subject { 8 | Subject get apiEncode => has((x) => x.apiEncode(), 'apiEncode()'); 9 | } 10 | 11 | extension DmNarrowChecks on Subject { 12 | Subject> get allRecipientIds => has((x) => x.allRecipientIds, 'allRecipientIds'); 13 | Subject> get otherRecipientIds => has((x) => x.otherRecipientIds, 'otherRecipientIds'); 14 | } 15 | 16 | extension TopicNarrowChecks on Subject { 17 | Subject get streamId => has((x) => x.streamId, 'streamId'); 18 | Subject get topic => has((x) => x.topic, 'topic'); 19 | Subject get with_ => has((x) => x.with_, 'with_'); 20 | } 21 | -------------------------------------------------------------------------------- /test/model/recent_dm_conversations_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:zulip/model/narrow.dart'; 4 | import 'package:zulip/model/recent_dm_conversations.dart'; 5 | 6 | extension RecentDmConversationsViewChecks on Subject { 7 | Subject> get map => has((v) => v.map, 'map'); 8 | Subject> get sorted => has((v) => v.sorted, 'sorted'); 9 | Subject> get latestMessagesByRecipient => has( 10 | (v) => v.latestMessagesByRecipient, 'latestMessagesByRecipient'); 11 | } 12 | -------------------------------------------------------------------------------- /test/model/saved_snippet_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:zulip/api/model/events.dart'; 4 | import 'package:zulip/api/model/model.dart'; 5 | 6 | import '../api/model/model_checks.dart'; 7 | import '../example_data.dart' as eg; 8 | import 'store_checks.dart'; 9 | 10 | void main() { 11 | test('handleSavedSnippetsEvent', () async { 12 | final store = eg.store(initialSnapshot: eg.initialSnapshot( 13 | savedSnippets: [eg.savedSnippet(id: 101)])); 14 | check(store).savedSnippets.values.single.id.equals(101); 15 | 16 | await store.handleEvent(SavedSnippetsAddEvent(id: 1, 17 | savedSnippet: eg.savedSnippet( 18 | id: 102, 19 | title: 'foo title', 20 | content: 'foo content', 21 | ))); 22 | check(store).savedSnippets.values.deepEquals(>[ 23 | (it) => it.isA().id.equals(101), 24 | (it) => it.isA()..id.equals(102) 25 | ..title.equals('foo title') 26 | ..content.equals('foo content') 27 | ]); 28 | 29 | await store.handleEvent(SavedSnippetsRemoveEvent(id: 1, savedSnippetId: 101)); 30 | check(store).savedSnippets.values.single.id.equals(102); 31 | 32 | await store.handleEvent(SavedSnippetsUpdateEvent(id: 1, 33 | savedSnippet: eg.savedSnippet( 34 | id: 102, 35 | title: 'bar title', 36 | content: 'bar content', 37 | dateCreated: store.savedSnippets.values.single.dateCreated, 38 | ))); 39 | check(store).savedSnippets.values.single 40 | ..id.equals(102) 41 | ..title.equals('bar title') 42 | ..content.equals('bar content'); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /test/model/schemas/drift_schema_v1.json: -------------------------------------------------------------------------------- 1 | {"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.0.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"accounts","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"realm_url","getter_name":"realmUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"api_key","getter_name":"apiKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_version","getter_name":"zulipVersion","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_merge_base","getter_name":"zulipMergeBase","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_feature_level","getter_name":"zulipFeatureLevel","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"unique_keys":[["realm_url","user_id"],["realm_url","email"]]}}]} -------------------------------------------------------------------------------- /test/model/schemas/drift_schema_v2.json: -------------------------------------------------------------------------------- 1 | {"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"accounts","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"realm_url","getter_name":"realmUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const UriConverter()","dart_type_name":"Uri"}},{"name":"user_id","getter_name":"userId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"email","getter_name":"email","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"api_key","getter_name":"apiKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_version","getter_name":"zulipVersion","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_merge_base","getter_name":"zulipMergeBase","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"zulip_feature_level","getter_name":"zulipFeatureLevel","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"acked_push_token","getter_name":"ackedPushToken","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[],"unique_keys":[["realm_url","user_id"],["realm_url","email"]]}}]} -------------------------------------------------------------------------------- /test/model/schemas/schema.dart: -------------------------------------------------------------------------------- 1 | // dart format width=80 2 | // GENERATED CODE, DO NOT EDIT BY HAND. 3 | // ignore_for_file: type=lint 4 | import 'package:drift/drift.dart'; 5 | import 'package:drift/internal/migrations.dart'; 6 | import 'schema_v1.dart' as v1; 7 | import 'schema_v2.dart' as v2; 8 | import 'schema_v3.dart' as v3; 9 | import 'schema_v4.dart' as v4; 10 | import 'schema_v5.dart' as v5; 11 | import 'schema_v6.dart' as v6; 12 | 13 | class GeneratedHelper implements SchemaInstantiationHelper { 14 | @override 15 | GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { 16 | switch (version) { 17 | case 1: 18 | return v1.DatabaseAtV1(db); 19 | case 2: 20 | return v2.DatabaseAtV2(db); 21 | case 3: 22 | return v3.DatabaseAtV3(db); 23 | case 4: 24 | return v4.DatabaseAtV4(db); 25 | case 5: 26 | return v5.DatabaseAtV5(db); 27 | case 6: 28 | return v6.DatabaseAtV6(db); 29 | default: 30 | throw MissingSchemaException(version, versions); 31 | } 32 | } 33 | 34 | static const versions = const [1, 2, 3, 4, 5, 6]; 35 | } 36 | -------------------------------------------------------------------------------- /test/model/unreads_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:collection/collection.dart'; 3 | import 'package:zulip/api/model/model.dart'; 4 | import 'package:zulip/model/narrow.dart'; 5 | import 'package:zulip/model/unreads.dart'; 6 | 7 | extension UnreadsChecks on Subject { 8 | Subject>>> get streams => has((u) => u.streams, 'streams'); 9 | Subject>> get dms => has((u) => u.dms, 'dms'); 10 | Subject> get mentions => has((u) => u.mentions, 'mentions'); 11 | Subject get oldUnreadsMissing => has((u) => u.oldUnreadsMissing, 'oldUnreadsMissing'); 12 | } 13 | -------------------------------------------------------------------------------- /test/notifications/receive_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:zulip/notifications/receive.dart'; 5 | 6 | import '../model/binding.dart'; 7 | 8 | void main() { 9 | TestZulipBinding.ensureInitialized(); 10 | 11 | Future init() async { 12 | addTearDown(testBinding.reset); 13 | testBinding.firebaseMessagingInitialToken = '012abc'; 14 | addTearDown(NotificationService.debugReset); 15 | NotificationService.debugBackgroundIsolateIsLive = false; 16 | await NotificationService.instance.start(); 17 | } 18 | 19 | // The calls to firebaseMessagingOnMessage and firebaseMessagingOnBackgroundMessage 20 | // are tested end-to-end in `display_test.dart`, by posting FCM messages 21 | // to the respective streams and checking that the right logic then runs. 22 | 23 | // The token logic is tested end-to-end in `test/model/store_test.dart` in the 24 | // `UpdateMachine.registerNotificationToken` tests. 25 | 26 | group('permissions', () { 27 | testWidgets('request permission', (tester) async { 28 | await init(); 29 | check(testBinding.firebaseMessaging.takeRequestPermissionCalls()) 30 | .length.equals(1); 31 | }, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS})); 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /test/test_async.dart: -------------------------------------------------------------------------------- 1 | import 'package:test_api/hooks.dart'; 2 | 3 | /// Ensure the test runner will wait for the given future to complete 4 | /// before considering the current test complete. 5 | /// 6 | /// Consider using this function, instead of `await`, when a test invokes 7 | /// a check which is asynchronous and has no interaction with other tasks 8 | /// the test will do later. 9 | /// 10 | /// Use `await`, instead of this function, when it matters what order the 11 | /// rest of the test's logic runs in relative to the asynchronous work 12 | /// represented by the given future. In particular, when calling a function 13 | /// that performs setup for later logic in the test, the returned future 14 | /// should always be awaited. 15 | void finish(Future future) { 16 | final outstandingWork = TestHandle.current.markPending(); 17 | future.whenComplete(outstandingWork.complete); 18 | } 19 | -------------------------------------------------------------------------------- /test/test_clipboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | // Inspired by MockClipboard in test code in the Flutter tree: 4 | // https://github.com/flutter/flutter/blob/de26ad0a8/packages/flutter/test/widgets/clipboard_utils.dart 5 | class MockClipboard { 6 | MockClipboard(); 7 | 8 | dynamic clipboardData; 9 | 10 | Future handleMethodCall(MethodCall methodCall) async { 11 | switch (methodCall.method) { 12 | case 'Clipboard.getData': 13 | return clipboardData; 14 | case 'Clipboard.hasStrings': 15 | final clipboardDataMap = clipboardData as Map?; 16 | final text = clipboardDataMap?['text'] as String?; 17 | return {'value': text != null && text.isNotEmpty}; 18 | case 'Clipboard.setData': 19 | clipboardData = methodCall.arguments; 20 | default: 21 | if (methodCall.method.startsWith('Clipboard.')) { 22 | throw UnimplementedError(); 23 | } 24 | } 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/test_navigation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | // Inspired by test code in the Flutter tree: 4 | // https://github.com/flutter/flutter/blob/53082f65b/packages/flutter/test/widgets/observer_tester.dart 5 | // https://github.com/flutter/flutter/blob/53082f65b/packages/flutter/test/widgets/navigator_test.dart 6 | 7 | /// A trivial observer for testing the navigator. 8 | class TestNavigatorObserver extends NavigatorObserver { 9 | void Function(Route topRoute, Route? previousTopRoute)? onChangedTop; 10 | void Function(Route route, Route? previousRoute)? onPushed; 11 | void Function(Route route, Route? previousRoute)? onPopped; 12 | void Function(Route route, Route? previousRoute)? onRemoved; 13 | void Function(Route? route, Route? previousRoute)? onReplaced; 14 | void Function(Route route, Route? previousRoute)? onStartUserGesture; 15 | void Function()? onStopUserGesture; 16 | 17 | @override 18 | void didChangeTop(Route topRoute, Route? previousTopRoute) { 19 | onChangedTop?.call(topRoute, previousTopRoute); 20 | } 21 | 22 | @override 23 | void didPush(Route route, Route? previousRoute) { 24 | onPushed?.call(route, previousRoute); 25 | } 26 | 27 | @override 28 | void didPop(Route route, Route? previousRoute) { 29 | onPopped?.call(route, previousRoute); 30 | } 31 | 32 | @override 33 | void didRemove(Route route, Route? previousRoute) { 34 | onRemoved?.call(route, previousRoute); 35 | } 36 | 37 | @override 38 | void didReplace({ Route? oldRoute, Route? newRoute }) { 39 | onReplaced?.call(newRoute, oldRoute); 40 | } 41 | 42 | @override 43 | void didStartUserGesture(Route route, Route? previousRoute) { 44 | onStartUserGesture?.call(route, previousRoute); 45 | } 46 | 47 | @override 48 | void didStopUserGesture() { 49 | onStopUserGesture?.call(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/test_share_plus.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:share_plus/share_plus.dart'; 3 | 4 | class MockSharePlus { 5 | MockSharePlus(); 6 | 7 | /// The mock [ShareResult.raw] that `shareWithResult` should give. 8 | String resultString = 'some-success-response'; 9 | 10 | /// The last string that `shareWithResult` was called with. 11 | String? sharedString; 12 | 13 | Future handleMethodCall(MethodCall methodCall) async { 14 | switch (methodCall.method) { 15 | case 'share': 16 | // The method channel doesn't preserve Map as 17 | // `arguments`'s type; logging runtimeType gives _Map. 18 | final arguments = methodCall.arguments as Map; 19 | sharedString = arguments['text'] as String; 20 | return resultString; 21 | default: 22 | throw UnimplementedError(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/widgets/channel_colors_checks.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:checks/checks.dart'; 4 | import 'package:zulip/widgets/channel_colors.dart'; 5 | 6 | extension ChannelColorSwatchChecks on Subject { 7 | Subject get base => has((s) => s.base, 'base'); 8 | Subject get unreadCountBadgeBackground => has((s) => s.unreadCountBadgeBackground, 'unreadCountBadgeBackground'); 9 | Subject get iconOnPlainBackground => has((s) => s.iconOnPlainBackground, 'iconOnPlainBackground'); 10 | Subject get iconOnBarBackground => has((s) => s.iconOnBarBackground, 'iconOnBarBackground'); 11 | Subject get barBackground => has((s) => s.barBackground, 'barBackground'); 12 | } 13 | -------------------------------------------------------------------------------- /test/widgets/code_block_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | import 'package:zulip/widgets/code_block.dart'; 5 | 6 | import '../model/binding.dart'; 7 | import 'test_app.dart'; 8 | 9 | void main() { 10 | TestZulipBinding.ensureInitialized(); 11 | 12 | group('CodeBlockTextStyles', () { 13 | group('lerp', () { 14 | Future contextWithZulipTheme(WidgetTester tester) async { 15 | addTearDown(testBinding.reset); 16 | await tester.pumpWidget(const TestZulipApp()); 17 | await tester.pump(); 18 | return tester.element(find.byType(Placeholder)); 19 | } 20 | 21 | testWidgets('light -> light', (tester) async { 22 | final context = await contextWithZulipTheme(tester); 23 | final a = CodeBlockTextStyles.light(context); 24 | final b = CodeBlockTextStyles.light(context); 25 | check(() => CodeBlockTextStyles.lerp(a, b, 0.5)).returnsNormally(); 26 | }); 27 | 28 | testWidgets('light -> dark', (tester) async { 29 | final context = await contextWithZulipTheme(tester); 30 | final a = CodeBlockTextStyles.light(context); 31 | final b = CodeBlockTextStyles.dark(context); 32 | check(() => CodeBlockTextStyles.lerp(a, b, 0.5)).returnsNormally(); 33 | }); 34 | 35 | testWidgets('dark -> light', (tester) async { 36 | final context = await contextWithZulipTheme(tester); 37 | final a = CodeBlockTextStyles.dark(context); 38 | final b = CodeBlockTextStyles.light(context); 39 | check(() => CodeBlockTextStyles.lerp(a, b, 0.5)).returnsNormally(); 40 | }); 41 | 42 | testWidgets('dark -> dark', (tester) async { 43 | final context = await contextWithZulipTheme(tester); 44 | final a = CodeBlockTextStyles.dark(context); 45 | final b = CodeBlockTextStyles.dark(context); 46 | check(() => CodeBlockTextStyles.lerp(a, b, 0.5)).returnsNormally(); 47 | }); 48 | }); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /test/widgets/color_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_checks/flutter_checks.dart'; 4 | import 'package:test/scaffolding.dart'; 5 | import 'package:zulip/widgets/color.dart'; 6 | 7 | void main() { 8 | group('ColorExtension', () { 9 | test('argbInt smoke', () { 10 | const testCases = [ 11 | 0xffffffff, 0x00000000, 0x12345678, 0x87654321, 0xfedcba98, 0x89abcdef]; 12 | 13 | for (final testCase in testCases) { 14 | check(Color(testCase).argbInt).equals(testCase); 15 | } 16 | }); 17 | 18 | const color = Color.fromRGBO(100, 200, 100, 0.5); 19 | 20 | test('withFadedAlpha smoke', () { 21 | check(color.withFadedAlpha(0.5)) 22 | .isSameColorAs(color.withValues(alpha: 0.25)); 23 | }); 24 | 25 | test('withFadedAlpha opaque color', () { 26 | const color = Colors.black; 27 | 28 | check(color.withFadedAlpha(0.5)) 29 | .isSameColorAs(color.withValues(alpha: 0.5)); 30 | }); 31 | 32 | test('withFadedAlpha factor > 1 fails', () { 33 | check(() => color.withFadedAlpha(1.1)).throws(); 34 | }); 35 | 36 | test('withFadedAlpha factor < 0 fails', () { 37 | check(() => color.withFadedAlpha(-0.1)).throws(); 38 | }); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/widgets/compose_box_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:zulip/widgets/compose_box.dart'; 4 | 5 | extension ComposeBoxStateChecks on Subject { 6 | Subject get controller => has((c) => c.controller, 'controller'); 7 | } 8 | 9 | extension ComposeBoxControllerChecks on Subject { 10 | Subject get content => has((c) => c.content, 'content'); 11 | Subject get contentFocusNode => has((c) => c.contentFocusNode, 'contentFocusNode'); 12 | } 13 | 14 | extension EditMessageComposeBoxControllerChecks on Subject { 15 | Subject get messageId => has((c) => c.messageId, 'messageId'); 16 | Subject get originalRawContent => has((c) => c.originalRawContent, 'originalRawContent'); 17 | } 18 | 19 | extension ComposeContentControllerChecks on Subject { 20 | Subject> get validationErrors => has((c) => c.validationErrors, 'validationErrors'); 21 | } 22 | -------------------------------------------------------------------------------- /test/widgets/content_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import 'package:zulip/widgets/content.dart'; 5 | 6 | extension RealmContentNetworkImageChecks on Subject { 7 | Subject get src => has((i) => i.src, 'src'); 8 | // TODO others 9 | } 10 | 11 | extension AvatarImageChecks on Subject { 12 | Subject get userId => has((i) => i.userId, 'userId'); 13 | } 14 | 15 | extension AvatarShapeChecks on Subject { 16 | Subject get size => has((i) => i.size, 'size'); 17 | Subject get borderRadius => has((i) => i.borderRadius, 'borderRadius'); 18 | Subject get child => has((i) => i.child, 'child'); 19 | } 20 | -------------------------------------------------------------------------------- /test/widgets/message_list_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/model/narrow.dart'; 3 | import 'package:zulip/widgets/message_list.dart'; 4 | 5 | extension MessageListPageChecks on Subject { 6 | Subject get initNarrow => has((x) => x.initNarrow, 'initNarrow'); 7 | } 8 | -------------------------------------------------------------------------------- /test/widgets/page_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:zulip/widgets/page.dart'; 4 | 5 | extension WidgetRouteChecks on Subject> { 6 | Subject get page => has((x) => x.page, 'page'); 7 | } 8 | 9 | extension AccountRouteChecks on Subject> { 10 | Subject get accountId => has((x) => x.accountId, 'accountId'); 11 | } 12 | -------------------------------------------------------------------------------- /test/widgets/profile_page_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:zulip/widgets/profile.dart'; 3 | 4 | extension ProfilePageChecks on Subject { 5 | Subject get userId => has((x) => x.userId, 'userId'); 6 | } 7 | -------------------------------------------------------------------------------- /test/widgets/store_checks.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:zulip/widgets/store.dart'; 4 | 5 | extension PerAccountStoreWidgetChecks on Subject { 6 | Subject get accountId => has((x) => x.accountId, 'accountId'); 7 | Subject get child => has((x) => x.child, 'child'); 8 | } 9 | -------------------------------------------------------------------------------- /test/widgets/unread_count_badge_checks.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:checks/checks.dart'; 4 | import 'package:zulip/widgets/unread_count_badge.dart'; 5 | 6 | extension UnreadCountBadgeChecks on Subject { 7 | Subject get count => has((b) => b.count, 'count'); 8 | Subject get bold => has((b) => b.bold, 'bold'); 9 | Subject get backgroundColor => has((b) => b.backgroundColor, 'backgroundColor'); 10 | } 11 | -------------------------------------------------------------------------------- /test/widgets/unread_count_badge_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:checks/checks.dart'; 2 | import 'package:flutter_checks/flutter_checks.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:zulip/widgets/channel_colors.dart'; 7 | import 'package:zulip/widgets/unread_count_badge.dart'; 8 | 9 | import '../model/binding.dart'; 10 | import 'test_app.dart'; 11 | 12 | void main() { 13 | TestZulipBinding.ensureInitialized(); 14 | 15 | group('UnreadCountBadge', () { 16 | testWidgets('smoke test; no crash', (tester) async { 17 | addTearDown(testBinding.reset); 18 | await tester.pumpWidget(const TestZulipApp( 19 | child: UnreadCountBadge(count: 1, backgroundColor: null))); 20 | await tester.pump(); 21 | tester.widget(find.text("1")); 22 | }); 23 | 24 | group('background', () { 25 | Future prepare(WidgetTester tester, Color? backgroundColor) async { 26 | addTearDown(testBinding.reset); 27 | await tester.pumpWidget(TestZulipApp( 28 | child: UnreadCountBadge(count: 1, backgroundColor: backgroundColor))); 29 | await tester.pump(); 30 | } 31 | 32 | Color? findBackgroundColor(WidgetTester tester) { 33 | final widget = tester.widget(find.byType(DecoratedBox)); 34 | final decoration = widget.decoration as BoxDecoration; 35 | return decoration.color; 36 | } 37 | 38 | testWidgets('default color', (tester) async { 39 | await prepare(tester, null); 40 | check(findBackgroundColor(tester)).isNotNull().isSameColorAs(const Color(0x26666699)); 41 | }); 42 | 43 | testWidgets('specified color', (tester) async { 44 | await prepare(tester, Colors.pink); 45 | check(findBackgroundColor(tester)).isNotNull().isSameColorAs(Colors.pink); 46 | }); 47 | 48 | testWidgets('stream color', (tester) async { 49 | final swatch = ChannelColorSwatch.light(0xff76ce90); 50 | await prepare(tester, swatch); 51 | check(findBackgroundColor(tester)).isNotNull().isSameColorAs(swatch.unreadCountBadgeBackground); 52 | }); 53 | }); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /tools/bump-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | usage () { 5 | cat <&2 6 | usage: bump-version 7 | 8 | Increments the version number in our pubspec and changelog. 9 | EOF 10 | exit 2 11 | } 12 | 13 | shift && usage; 14 | 15 | date="$(date --iso)" 16 | 17 | old_version_int="$(perl -lne ' 18 | print $1 if (/^version: 0\.0\.(\d+)\+\1$/); 19 | ' pubspec.yaml)" 20 | version_int=$(( old_version_int + 1 )) 21 | version_name="0.0.${version_int}" 22 | tag_name=v"${version_name}" 23 | 24 | had_uncommitted= 25 | if ! git diff-index --quiet HEAD; then 26 | had_uncommitted=1 27 | fi 28 | 29 | perl -i -0pe ' 30 | s<^version: \K0\.0\.(\d+)\+\1$> 31 | <0.0.'"${version_int}"'+'"${version_int}"'>m; 32 | ' pubspec.yaml 33 | 34 | perl -i -0pe ' 35 | s/^\#\#\ Unreleased\n\K 36 | / 37 | 38 | ## '"${version_name}"' ('"${date}"') 39 | /xms 40 | ' docs/changelog.md 41 | 42 | msg="version: Bump version to $version_name" 43 | 44 | if [ -n "$had_uncommitted" ]; then 45 | { 46 | echo "There were uncommitted changes. To commit:" 47 | echo " git commit -am ${msg@Q}" 48 | # NB if tempted to use the ${...@Q} feature: it's new in bash 4.4, 49 | # released 2016-09, found in stretch and bionic but not xenial. 50 | echo 51 | echo "Then tag the commit:" 52 | echo " git tag ${tag_name}" 53 | echo "inspect the result, and push:" 54 | echo " git push origin main ${tag_name}" 55 | } >&2 56 | exit 1 57 | fi 58 | 59 | git commit -am "$msg" 60 | 61 | git tag "${tag_name}" 62 | 63 | { 64 | echo 65 | echo "Committed and tagged." 66 | echo "Inspect the result, then push; for example:" 67 | echo " git push origin main ${tag_name}" 68 | } >&2 69 | -------------------------------------------------------------------------------- /tools/content/model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | /// A data structure representing a message. 5 | final class MessageEntry { 6 | const MessageEntry({ 7 | required this.id, 8 | required this.content, 9 | }); 10 | 11 | /// Selectively parses from get-message responses. 12 | /// 13 | /// See also: https://zulip.com/api/get-messages#response 14 | factory MessageEntry.fromJson(Map json) { 15 | try { 16 | return MessageEntry( 17 | id: (json['id'] as num).toInt(), content: json['content'] as String); 18 | } catch (e) { 19 | throw FormatException( 20 | 'Malformed corpus data entry. Got: $e\n' 21 | 'When parsing: $json'); 22 | } 23 | } 24 | 25 | Map toJson() => {'id': id, 'content': content}; 26 | 27 | /// The message ID, unique within a server. 28 | final int id; 29 | 30 | /// The rendered HTML of the message. 31 | final String content; 32 | } 33 | 34 | /// Open the given JSON Lines file and read [MessageEntry]'s from it. 35 | /// 36 | /// We store the entries in JSON Lines format and return them from a stream to 37 | /// avoid excessive use of memory. 38 | Stream readMessagesFromJsonl(File file) => file.openRead() 39 | .transform(utf8.decoder).transform(const LineSplitter()) 40 | .map(jsonDecode).map((x) => MessageEntry.fromJson(x as Map)); 41 | -------------------------------------------------------------------------------- /tools/customer-testing/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Setup script for running Zulip's tests as part of the Flutter 3 | # "customer testing" suite: 4 | # https://github.com/flutter/tests 5 | 6 | set -euo pipefail 7 | 8 | # Flutter's "customer testing" suite runs in two environments: 9 | # * GitHub Actions for changes to the flutter/tests tree itself 10 | # (which is just a registry of downstream test suites to run); 11 | # * LUCI, at ci.chromium.org, for changes in Flutter. 12 | # 13 | # For background, see: 14 | # https://github.com/flutter/flutter/issues/162041#issuecomment-2611129958 15 | 16 | if ! sudo -v; then 17 | # In the LUCI environment sudo isn't available, 18 | # but also the setup below isn't needed. Skip it. 19 | exit 0 20 | fi 21 | 22 | # Otherwise, assume we're in the GitHub Actions environment. 23 | 24 | 25 | # Install libsqlite3-dev. 26 | # 27 | # A few Zulip tests use SQLite, and so need libsqlite3.so. 28 | # (The actual databases involved are tiny and in-memory.) 29 | # 30 | # Both older and newer GitHub Actions images have the SQLite shared 31 | # library, from the libsqlite3-0 package. But newer images 32 | # (ubuntu-24.04, which the ubuntu-latest alias switched to around 33 | # 2025-01) no longer have a symlink "libsqlite3.so" pointing to it, 34 | # which is part of the libsqlite3-dev package. Install that. 35 | sudo apt install -y libsqlite3-dev 36 | -------------------------------------------------------------------------------- /tools/gradle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | this_dir=${BASH_SOURCE[0]%/*} 5 | # shellcheck source=tools/lib/ensure-coreutils.sh 6 | . "${this_dir}"/lib/ensure-coreutils.sh 7 | root_dir=$(readlink -f "${this_dir}"/..) 8 | 9 | exec "${root_dir}"/android/gradlew -p "${root_dir}"/android "$@" 10 | -------------------------------------------------------------------------------- /tools/icons/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /tools/icons/build-icon-font: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Rebuild our custom icon font ZulipIcons.ttf, and the ZulipIcons class. 3 | # 4 | # To use these icons, use the ZulipIcons class in the same way 5 | # as one uses the Flutter Material library's Icons class. 6 | # 7 | # To add a new icon, see comments on the ZulipIcons class. 8 | 9 | set -euo pipefail 10 | 11 | this_dir=${BASH_SOURCE[0]%/*} 12 | 13 | cd "${this_dir}" 14 | 15 | # `npm install` is painfully verbose, so we dial that back. 16 | npm install --no-fund --no-audit \ 17 | | grep -v '^up to date in ' 18 | 19 | node build-icon-font.js 20 | -------------------------------------------------------------------------------- /tools/icons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "@vusion/webfonts-generator": "^0.8.0" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tools/lib/cli.sh: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | # 3 | # Shell functions for building CLIs (command-line interfaces). 4 | 5 | # Run the given command, after printing the command for the user. 6 | # 7 | # This works by temporarily setting `set -x`. 8 | run_visibly () { 9 | set -x 10 | "$@" 11 | { set +x; } 2>/dev/null 12 | } 13 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void RegisterPlugins(flutter::PluginRegistry* registry) { 16 | FileSelectorWindowsRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 18 | FirebaseCorePluginCApiRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); 20 | SharePlusWindowsPluginCApiRegisterWithRegistrar( 21 | registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); 22 | Sqlite3FlutterLibsPluginRegisterWithRegistrar( 23 | registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); 24 | UrlLauncherWindowsRegisterWithRegistrar( 25 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 26 | } 27 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_windows 7 | firebase_core 8 | share_plus 9 | sqlite3_flutter_libs 10 | url_launcher_windows 11 | ) 12 | 13 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 14 | ) 15 | 16 | set(PLUGIN_BUNDLED_LIBRARIES) 17 | 18 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 19 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 20 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 23 | endforeach(plugin) 24 | 25 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 26 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 27 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 28 | endforeach(ffi_plugin) 29 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | return true; 35 | } 36 | 37 | void FlutterWindow::OnDestroy() { 38 | if (flutter_controller_) { 39 | flutter_controller_ = nullptr; 40 | } 41 | 42 | Win32Window::OnDestroy(); 43 | } 44 | 45 | LRESULT 46 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 47 | WPARAM const wparam, 48 | LPARAM const lparam) noexcept { 49 | // Give Flutter, including plugins, an opportunity to handle window messages. 50 | if (flutter_controller_) { 51 | std::optional result = 52 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 53 | lparam); 54 | if (result) { 55 | return *result; 56 | } 57 | } 58 | 59 | switch (message) { 60 | case WM_FONTCHANGE: 61 | flutter_controller_->engine()->ReloadSystemFonts(); 62 | break; 63 | } 64 | 65 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 66 | } 67 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"Zulip", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zulip/zulip-flutter/834834b514ebc019f6ed80785d792009eb29a1e0/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | std::string utf8_string; 52 | if (target_length == 0 || target_length > utf8_string.max_size()) { 53 | return utf8_string; 54 | } 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------