├── .env.example
├── .firebaserc
├── .fvm
├── .gitignore
└── fvm_config.json
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yaml
│ ├── config.yaml
│ ├── feature_request.yaml
│ └── improve.yaml
├── PULL_REQUEST_TEMPLATE.md
├── labeler.yml
└── workflows
│ ├── auto-format.yaml
│ ├── auto_author.yml
│ ├── firebase-hosting-preview.yml
│ ├── gh-pages.yml
│ ├── label.yml
│ └── pr-review.yml
├── .gitignore
├── .metadata
├── .run
├── Mock debug.run.xml
├── Production debug.run.xml
└── Production profile.run.xml
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── CONTRIBUTING.ja.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── all_lint_rules.yaml
├── analysis_options.yaml
├── assets
├── Illustration-Conference.svg
├── app_links
│ ├── app_store.png
│ └── google_play.png
├── flutterkaigi_logo_shadowed.svg
├── flutterkaigi_logo_union.svg
├── icons
│ ├── copy.svg
│ ├── discord.svg
│ ├── dot.svg
│ ├── github.svg
│ ├── medium.svg
│ ├── note.svg
│ ├── qiita.svg
│ ├── twitter.svg
│ └── zenn.svg
└── sponsors
│ ├── bitkey.svg
│ ├── cyberagent.png
│ ├── demaecan.svg
│ ├── diverse.svg
│ ├── enechain.svg
│ ├── flutter.svg
│ ├── harmo.png
│ ├── layerx.svg
│ ├── magicpod.svg
│ ├── mthree.svg
│ ├── navitime.svg
│ ├── pioneer.svg
│ ├── recruit.png
│ ├── studyplus.svg
│ ├── tenx.svg
│ ├── tokyu.svg
│ ├── youtrust.svg
│ └── yumemi.svg
├── build.yaml
├── firebase.json
├── flavor
├── mock.json
└── production.json
├── l10n.yaml
├── lib
├── app
│ ├── app.dart
│ ├── config.dart
│ ├── config.g.dart
│ ├── home_page.dart
│ ├── router
│ │ ├── router.dart
│ │ └── router.g.dart
│ ├── session_page.dart
│ ├── sessions_page.dart
│ ├── sponsor_page.dart
│ └── sponsor_page.g.dart
├── core
│ ├── components
│ │ ├── copy_url_button.dart
│ │ ├── finish_snack_bar.dart
│ │ ├── flutter_kaigi_logo.dart
│ │ ├── left_filled_icon_button.dart
│ │ ├── list_bullet.dart
│ │ ├── profile_image.dart
│ │ ├── responsive_widget.dart
│ │ ├── section_header.dart
│ │ ├── social_share.dart
│ │ ├── tweet_button.dart
│ │ └── wanted.dart
│ ├── foundation
│ │ ├── duration_ex.dart
│ │ └── iterable_ex.dart
│ ├── gen
│ │ └── assets.gen.dart
│ ├── l10n
│ │ ├── app_en.arb
│ │ └── app_ja.arb
│ ├── theme.dart
│ └── theme
│ │ ├── app_text_style.dart
│ │ ├── app_theme.dart
│ │ ├── baseline_color_scheme.dart
│ │ ├── dimension.dart
│ │ └── gradient.dart
├── features
│ ├── access
│ │ └── ui
│ │ │ └── access_widget.dart
│ ├── announcement
│ │ └── ui
│ │ │ ├── announcement_section.dart
│ │ │ └── announcement_section_header.dart
│ ├── app_links
│ │ └── ui
│ │ │ ├── app_links.dart
│ │ │ ├── app_links_header.dart
│ │ │ ├── app_links_section.dart
│ │ │ └── app_links_summary.dart
│ ├── conference
│ │ └── model
│ │ │ └── target_day.dart
│ ├── count_down
│ │ ├── model
│ │ │ ├── count_down_timer.dart
│ │ │ ├── count_down_timer.g.dart
│ │ │ └── count_down_unit.dart
│ │ └── ui
│ │ │ ├── count_down_section.dart
│ │ │ └── count_down_unit_item.dart
│ ├── event
│ │ └── hands-on
│ │ │ ├── data
│ │ │ ├── hands_on_staffs_provider.dart
│ │ │ └── hands_on_staffs_provider.g.dart
│ │ │ └── ui
│ │ │ └── hands_on_event.dart
│ ├── footer
│ │ └── ui
│ │ │ ├── footer.dart
│ │ │ ├── footer_copyright.dart
│ │ │ ├── footer_links.dart
│ │ │ ├── footer_other_year_links.dart
│ │ │ └── footer_sns_links.dart
│ ├── header
│ │ ├── data
│ │ │ └── header_item_button_data.dart
│ │ └── ui
│ │ │ ├── flutter_kaigi_sns_links.dart
│ │ │ └── header_widget.dart
│ ├── hero
│ │ └── ui
│ │ │ ├── hero_section.dart
│ │ │ ├── hero_section_desktop.dart
│ │ │ ├── hero_section_mobile.dart
│ │ │ └── hero_section_twitter.dart
│ ├── news
│ │ ├── data
│ │ │ ├── mock_news_data_source.dart
│ │ │ ├── news.dart
│ │ │ ├── news.freezed.dart
│ │ │ ├── news.g.dart
│ │ │ ├── news_data_source.dart
│ │ │ ├── news_data_source.g.dart
│ │ │ ├── news_provider.dart
│ │ │ └── news_provider.g.dart
│ │ └── ui
│ │ │ └── news_section.dart
│ ├── session
│ │ ├── data
│ │ │ ├── session.dart
│ │ │ ├── session.freezed.dart
│ │ │ ├── session.g.dart
│ │ │ ├── session_data_source.dart
│ │ │ ├── session_provider.dart
│ │ │ ├── session_provider.g.dart
│ │ │ ├── speaker.dart
│ │ │ ├── speaker.freezed.dart
│ │ │ ├── speaker.g.dart
│ │ │ ├── tag.dart
│ │ │ ├── tag.freezed.dart
│ │ │ ├── tag.g.dart
│ │ │ ├── track.dart
│ │ │ ├── track.freezed.dart
│ │ │ └── track.g.dart
│ │ └── ui
│ │ │ ├── detail
│ │ │ ├── session_detail.dart
│ │ │ └── session_detail_content.dart
│ │ │ ├── list
│ │ │ ├── sessions_caution.dart
│ │ │ ├── sessions_section_header.dart
│ │ │ ├── sessions_table.dart
│ │ │ ├── sessions_table_card.dart
│ │ │ └── sessions_tracker_header.dart
│ │ │ └── session_lunch_item.dart
│ ├── sponsor
│ │ ├── data
│ │ │ ├── sponsor.dart
│ │ │ ├── sponsor.freezed.dart
│ │ │ ├── sponsor_data_source.dart
│ │ │ ├── sponsor_data_source.g.dart
│ │ │ ├── sponsor_plan.dart
│ │ │ ├── sponsor_session.dart
│ │ │ └── sponsor_session.freezed.dart
│ │ └── ui
│ │ │ ├── detail
│ │ │ ├── sponsor_detail.dart
│ │ │ ├── sponsor_detail.g.dart
│ │ │ ├── sponsor_detail_content.dart
│ │ │ ├── sponsor_detail_logo_card.dart
│ │ │ ├── sponsor_introduction.dart
│ │ │ └── sponsor_session.dart
│ │ │ ├── list
│ │ │ ├── sponsor_logo_cards.dart
│ │ │ └── sponsors_section.dart
│ │ │ ├── sponsor_plan_header.dart
│ │ │ └── sponsor_section_header.dart
│ └── staff
│ │ ├── data
│ │ ├── mock_staff_data_source.dart
│ │ ├── staff.dart
│ │ ├── staff.freezed.dart
│ │ ├── staff.g.dart
│ │ ├── staff_data_source.dart
│ │ ├── staff_data_source.g.dart
│ │ ├── staff_provider.dart
│ │ └── staff_provider.g.dart
│ │ └── ui
│ │ ├── divider_with_title.dart
│ │ ├── sns_icon.dart
│ │ ├── staff_header.dart
│ │ ├── staff_item.dart
│ │ └── staff_table.dart
└── main.dart
├── pubspec.lock
├── pubspec.yaml
├── test
└── core
│ └── foundation
│ └── iterable_ex_test.dart
├── web
├── 404.html
├── assets
│ └── flutterkaigi_ogp.png
├── bundle.mjs
├── favicon.ico
├── favicon.svg
├── icons
│ └── apple-touch-icon.png
├── index.css
├── index.html
└── index.mjs
└── web_assets
├── .gitignore
├── Makefile
├── README.md
├── index.js
├── package-lock.json
└── package.json
/.env.example:
--------------------------------------------------------------------------------
1 | NEWT_CDN_API_TOKEN=
2 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "flutterkaigi-2023-preview"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.fvm/.gitignore:
--------------------------------------------------------------------------------
1 | flutter_sdk
2 |
--------------------------------------------------------------------------------
/.fvm/fvm_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "flutterSdkVersion": "3.14.0-0.2.pre@beta",
3 | "flavors": {}
4 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.freezed.dart linguist-generated=true
2 | *.g.dart linguist-generated=true
3 | *.gen.dart linguist-generated=true
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Create a report to help us improve
3 | title: "[Bug]: "
4 | labels: [ "bug", "triage" ]
5 |
6 | body:
7 | - type: textarea
8 | id: describe
9 | attributes:
10 | label: Describe the bug
11 | description: A clear and concise description of what the bug is.
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: screenshots
16 | attributes:
17 | label: Screenshots
18 | description: If applicable, add screenshots to help explain your problem.
19 | value: |
20 |
21 |
22 | validations:
23 | required: false
24 | - type: textarea
25 | id: reproduce
26 | attributes:
27 | label: To Reproduce
28 | description: Steps to reproduce the behavior
29 | value: |
30 | 1. Go to '...'
31 | 2. Click on '....'
32 | 3. Scroll down to '....'
33 | 4. See error
34 | validations:
35 | required: true
36 | - type: textarea
37 | id: expected
38 | attributes:
39 | label: Expected behavior
40 | description: A clear and concise description of what you expected to happen.
41 | validations:
42 | required: true
43 | - type: textarea
44 | id: env
45 | attributes:
46 | label: Environment
47 | description: Please provide details about your environment.
48 | value: |
49 | - Device: [e.g. iPhone6]
50 | - OS: [e.g. iOS 8.1]
51 | - Browser: [e.g. Chrome, Safari]
52 | - Version: [e.g. 1.0.0+1]
53 | - Commit SHA: [e.g. 486ba7defaf1bd6b0deb87db2118c328e5baf529]
54 | validations:
55 | required: true
56 | - type: textarea
57 | id: additional
58 | attributes:
59 | label: Additional context
60 | description: Add any other context about the problem here.
61 | validations:
62 | required: false
63 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yaml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: FlutterKaigi Community Support
4 | url: https://github.com/FlutterKaigi/2023/discussions
5 | about: Please ask and answer questions here.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea for this project
3 | title: "[Feature Request]: "
4 | labels: [ "feature", "triage" ]
5 |
6 | body:
7 | - type: textarea
8 | id: describe
9 | attributes:
10 | label: Is your feature request related to a problem? Please describe
11 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: solution
16 | attributes:
17 | label: Describe the solution you'd like
18 | description: A clear and concise description of what you want to happen.
19 | validations:
20 | required: true
21 | - type: textarea
22 | id: alternatives
23 | attributes:
24 | label: Describe alternatives you've considered
25 | description: A clear and concise description of any alternative solutions or features you've considered.
26 | validations:
27 | required: false
28 | - type: textarea
29 | id: additional
30 | attributes:
31 | label: Additional context
32 | description: Add any other context about the problem here.
33 | validations:
34 | required: false
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/improve.yaml:
--------------------------------------------------------------------------------
1 | name: Improve
2 | description: Create a report to help us improve
3 | title: "[Improve]: "
4 | labels: [ "improve", "triage" ]
5 |
6 | body:
7 | - type: textarea
8 | id: describe
9 | attributes:
10 | label: Describe
11 | description: A clear and concise description.
12 | validations:
13 | required: true
14 | - type: textarea
15 | id: screenshots
16 | attributes:
17 | label: Screenshots
18 | description: If applicable, add screenshots to help explain your problem.
19 | value: |
20 |
21 |
22 | validations:
23 | required: false
24 | - type: textarea
25 | id: env
26 | attributes:
27 | label: Environment
28 | description: Please provide details about your environment.
29 | value: |
30 | - Device: [e.g. iPhone6]
31 | - OS: [e.g. iOS 8.1]
32 | - Browser: [e.g. Chrome, Safari]
33 | - Version: [e.g. 1.0.0+1]
34 | - Commit SHA: [e.g. 486ba7defaf1bd6b0deb87db2118c328e5baf529]
35 | validations:
36 | required: true
37 | - type: textarea
38 | id: additional
39 | attributes:
40 | label: Additional context
41 | description: Add any other context about the problem here.
42 | validations:
43 | required: false
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Issue
2 |
3 | - close #ISSUE_NUMBER
4 |
5 | ## Overview (Required)
6 |
7 | -
8 |
9 | ## Links
10 |
11 | -
12 |
13 | ## Screenshot
14 |
15 | | Before | After |
16 | |:--------------------------:|:--------------------------:|
17 | |
|
|
18 |
--------------------------------------------------------------------------------
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | feature: feature/*
2 | bug: ['fix/*', 'bugfix/*']
3 | improve: ['improvement/*', 'improve/*', 'migrate/*']
4 |
--------------------------------------------------------------------------------
/.github/workflows/auto-format.yaml:
--------------------------------------------------------------------------------
1 | name: Auto Format
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | format:
8 | runs-on: ubuntu-latest
9 | timeout-minutes: 15
10 |
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: Fetch flutter config
14 | uses: kuhnroyal/flutter-fvm-config-action@v1
15 | - name: Setup Flutter
16 | uses: subosito/flutter-action@v2
17 | with:
18 | flutter-version: ${{ env.FLUTTER_VERSION }}
19 | channel: ${{ env.FLUTTER_CHANNEL }}
20 | cache: true
21 | - name: Install dependencies
22 | run: |
23 | flutter pub get
24 |
25 | - name: Format
26 | run: |
27 | files=$(find lib test -name "*.dart" -not \( -name "*.*freezed.dart" -o -name "*.*g.dart" -o -name "*.gen.dart" \))
28 | for file in $files; do
29 | # limit jobs to 5
30 | if [ "$(jobs -r | wc -l)" -ge 5 ]; then
31 | wait "$(jobs -r -p | head -1)"
32 | fi
33 | dart fix --apply "$file" &
34 | done
35 | wait
36 |
37 | dart format "$files"
38 |
39 | # 変更が発生した場合は プルリクエストを作成
40 | - name: Create pull request
41 | uses: peter-evans/create-pull-request@v5
42 | with:
43 | delete-branch: true
44 | commit-message: "Auto format"
45 | committer: "GitHub Actions "
46 | base: ${{ github.head_ref }}
47 | branch: "auto-format/${{ github.sha }}"
48 | title: "Auto format - ref: ${{ github.ref_name }}"
49 | body: "Auto format by GitHub Actions on ${{ github.sha }}\nby: ${{github.actor}}"
50 |
51 |
--------------------------------------------------------------------------------
/.github/workflows/auto_author.yml:
--------------------------------------------------------------------------------
1 | on:
2 | pull_request:
3 | types: [opened]
4 | name: Assign author
5 | jobs:
6 | assignAuthor:
7 | name: Assign author to PR
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Assign author to PR
11 | uses: technote-space/assign-author@v1
12 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-preview.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Firebase hosting
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build-deploy:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@master
11 |
12 | - name: Fetch flutter config
13 | uses: kuhnroyal/flutter-fvm-config-action@v1
14 |
15 | - name: Setup Flutter
16 | uses: subosito/flutter-action@v2
17 | with:
18 | flutter-version: ${{ env.FLUTTER_VERSION }}
19 | channel: ${{ env.FLUTTER_CHANNEL }}
20 | cache: true
21 |
22 | - name: Install dependencies
23 | run: |
24 | flutter pub get
25 |
26 | - name: create .env
27 | run: |
28 | touch .env
29 | echo NEWT_CDN_API_TOKEN=${{secrets.NEWT_CDN_API_TOKEN}} >> .env
30 |
31 | - name: Transpile
32 | run: |
33 | flutter build web \
34 | --web-renderer html \
35 | --dart-define-from-file flavor/production.json \
36 | --profile
37 |
38 | - name: Deploy
39 | uses: FirebaseExtended/action-hosting-deploy@v0
40 | with:
41 | repoToken: "${{ secrets.GITHUB_TOKEN }}"
42 | firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_FLUTTERKAIGI }}"
43 | projectId: flutterkaigi-2023-preview
44 | env:
45 | FIREBASE_CLI_PREVIEWS: hostingchannels
46 |
--------------------------------------------------------------------------------
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Github pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build-deploy:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 10
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Fetch flutter config
17 | uses: kuhnroyal/flutter-fvm-config-action@v1
18 |
19 | - name: Setup Flutter
20 | uses: subosito/flutter-action@v2
21 | with:
22 | flutter-version: ${{ env.FLUTTER_VERSION }}
23 | channel: ${{ env.FLUTTER_CHANNEL }}
24 | cache: true
25 |
26 | - name: Install dependencies
27 | run: |
28 | flutter pub get
29 |
30 | - name: create .env
31 | run: |
32 | touch .env
33 | echo NEWT_CDN_API_TOKEN=${{secrets.NEWT_CDN_API_TOKEN}} >> .env
34 |
35 | - name: Transpile
36 | run: |
37 | flutter build web \
38 | --web-renderer html \
39 | --dart-define-from-file flavor/production.json \
40 | --base-href "/2023/" \
41 | --release
42 |
43 | - name: Deploy
44 | uses: peaceiris/actions-gh-pages@v3
45 | with:
46 | github_token: ${{ secrets.GITHUB_TOKEN }}
47 | publish_dir: ./build/web
48 |
--------------------------------------------------------------------------------
/.github/workflows/label.yml:
--------------------------------------------------------------------------------
1 | name: PR Labeler
2 | on:
3 | pull_request:
4 | types: [opened]
5 |
6 | jobs:
7 | label:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: TimonVS/pr-labeler-action@v3
11 | with:
12 | configuration-path: .github/labeler.yml
13 | env:
14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/pr-review.yml:
--------------------------------------------------------------------------------
1 | name: PR review
2 |
3 | on:
4 | pull_request:
5 |
6 | jobs:
7 | analyze:
8 | runs-on: ubuntu-latest
9 | timeout-minutes: 10
10 |
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: Fetch flutter config
14 | uses: kuhnroyal/flutter-fvm-config-action@v1
15 | - name: Setup Flutter
16 | uses: subosito/flutter-action@v2
17 | with:
18 | flutter-version: ${{ env.FLUTTER_VERSION }}
19 | channel: ${{ env.FLUTTER_CHANNEL }}
20 | cache: true
21 | - name: Install dependencies
22 | run: |
23 | flutter pub get
24 | - uses: invertase/github-action-dart-analyzer@v1
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .firebase
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Exceptions to above rules.
42 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
43 |
44 | .env
45 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
17 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
18 | - platform: linux
19 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
20 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
21 | - platform: macos
22 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
23 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
24 | - platform: web
25 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
26 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
27 | - platform: windows
28 | create_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
29 | base_revision: 9cd3d0d9ff05768afa249e036acc66e8abe93bff
30 |
31 | # User provided section
32 |
33 | # List of Local paths (relative to this file) that should be
34 | # ignored by the migrate tool.
35 | #
36 | # Files that are not part of the templates will be ignored by default.
37 | unmanaged_files:
38 | - 'lib/main.dart'
39 | - 'ios/Runner.xcodeproj/project.pbxproj'
40 |
--------------------------------------------------------------------------------
/.run/Mock debug.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.run/Production debug.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.run/Production profile.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "davidanson.vscode-markdownlint",
4 | "dart-code.flutter",
5 | "dart-code.dart-code",
6 | "redhat.vscode-yaml"
7 | ]
8 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Mock debug",
9 | "request": "launch",
10 | "type": "dart",
11 | "toolArgs": [
12 | "--dart-define-from-file=flavor/mock.json",
13 | "--web-renderer=html"
14 | ]
15 | },
16 | {
17 | "name": "Production debug",
18 | "request": "launch",
19 | "type": "dart",
20 | "toolArgs": [
21 | "--dart-define-from-file=flavor/production.json",
22 | "--web-renderer=html"
23 | ]
24 | },
25 | {
26 | "name": "Production profile",
27 | "request": "launch",
28 | "type": "dart",
29 | "flutterMode": "profile",
30 | "toolArgs": [
31 | "--dart-define-from-file=flavor/production.json",
32 | "--web-renderer=html"
33 | ]
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.flutterSdkPath": ".fvm/flutter_sdk",
3 | "search.exclude": {
4 | "**/.fvm": true
5 | },
6 | "files.watcherExclude": {
7 | "**/.fvm": true
8 | },
9 | "explorer.fileNesting.enabled": true,
10 | "explorer.fileNesting.patterns": {
11 | "*.dart": "$(capture).g.dart, $(capture).freezed.dart"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.ja.md:
--------------------------------------------------------------------------------
1 | # コントリビュート
2 |
3 | あなたのコントリビュートをお待ちしております!
4 |
5 | ## コントリビュート方法
6 |
7 | ### タスクの見つけ方
8 |
9 | タスク管理に GitHub Issue を使っています。こちらでコントリビュートしたい Issue をお探しください。 Issue がない Pull Request でも大丈夫です。その場合は Pull Request に理由、原因、解決策などの詳細をご記入ください。
10 |
11 | ### コントリビュートの始め方
12 |
13 | もし取り組みたいタスクを見つけたら、他の人と重複して作業しないようにするため Issue に "🙋" などのコメントをしてください。なるべく早くいただいたコメントにリアクションしますが Issue にコメントを書いたらタスクを始めていただいて構いません。
14 |
15 | ## ウェブサイト FlutterKaigi.jp の改善に貢献する
16 |
17 | ウェブサイト FlutterKaigi.jp (以下・当ウェブサイト) の改善にご協力いただく際の方法について説明します。
18 |
19 | ### バグを報告する
20 |
21 | 当ウェブサイトの改善に貢献する方法の中でも、非常に簡単かつ効果的なものとして、バグの報告があります。
22 |
23 | ### コーディングに貢献する
24 |
25 | FlutterKaigi 実行委員会はコード修正案の投稿を歓迎しており精力的に審査しています。ぜひソースコードをチェックアウトして特定のバグや機能を対象とするコード修正案をご投稿ください。
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution
2 |
3 | We look forward to your contribution!
4 |
5 | ## How to contribute
6 |
7 | ### How to find a task
8 |
9 | We are using GitHub Issue for task management. Please find the Issue you want to contribute here. You can also make Pull Requests without relation to any issues. In this case, please provide details such as the reason, cause, and solution in the Pull Request.
10 |
11 | ### How to get started with a contribution
12 |
13 | If you find a task you want to work on, comment on the Issue, such as "🙋", to avoid duplicating work with others. We will react to the comments you received as soon as possible, but you can start the task after writing a comment on the Issue.
14 |
15 | ## Contribute to improving the website FlutterKaigi.jp
16 |
17 | We will explain how to help improve the website FlutterKaigi.jp (referred to as "this website" below).
18 |
19 | ### Report a bug
20 |
21 | One of the easiest and most effective ways to help improve this website is to report a bug.
22 |
23 | ### Contribute to coding
24 |
25 | The FlutterKaigi Executive Committee welcomes submissions of proposed code amendments and is vigorously reviewing them. Please check out the source code and post any code fixes that target specific bugs or features.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FlutterKaigi 2023 official website
2 |
3 | ## Development
4 |
5 | We will deliver sessions related to FlutterKaigi in accordance with [Figma].
6 |
7 | ### Setup
8 |
9 | This project uses [Flutter beta channel]. The reasons:
10 | - Use the latest Flutter/Dart features.
11 | - Use a more stable version than master channel.
12 |
13 | 1. [Install fvm].
14 | 1. Move to project root directory, and run `fvm install` command.
15 | 1. Run `fvm flutter pub get` command.
16 | 1. Set up IDE to use fvm.
17 | - If you use [VSCode], already set up.
18 | - If you use [Android Studio], please see [fvm document].
19 |
20 | ### Run app
21 |
22 | A Run Configuration is set up to run this app.
23 |
24 | Please check:
25 | - `.vscode/launch.json` for VS Code.
26 | - `.run/~.run.xml` for IntelliJ IDEA or Android Studio.
27 |
28 | ### Contributing
29 |
30 | We always welcome all contributions! See [CONTRIBUTING.md] for more information.
31 |
32 | For Japanese, please see [CONTRIBUTING.ja.md].
33 |
34 | ### Branch Rules
35 |
36 | We would be happy if you could create a branch with the following rules
37 |
38 | | branch prefix | label |
39 | | -- | -- |
40 | | `feature/*` | `feature` |
41 | | `fix/*`, `bugfix/*` | `bug` |
42 | | `improvement/*`, `improve/*`, `migrate/*` | `improve` |
43 |
44 | ### Tech Stacks
45 |
46 | - [Flutter]
47 | - [Flutter Web]
48 | - [GitHub Pages]
49 | - [Codemagic Static Pages]
50 |
51 | ### Directory Structure
52 |
53 | ```text
54 | ./lib
55 | ├── app
56 | ├── core
57 | │ ├── components
58 | │ ├── gen
59 | │ ├── l10n
60 | │ └── theme
61 | └── features
62 | ├── ...
63 | │ ├── data
64 | │ └── ui
65 | └── ...
66 | ├── data
67 | └── ui
68 | ```
69 |
70 | ### Architecture
71 |
72 | ```mermaid
73 | flowchart TB
74 | app
75 | core
76 | features
77 | main[main.dart]
78 |
79 | main --> app
80 | app --> core & features
81 | features --> core
82 | ```
83 |
84 | ## Thanks
85 |
86 | Thank you for contributing!
87 |
88 | ### Contributors
89 |
90 | GitHub: [Contributors]
91 |
92 |
93 |
94 | [Figma]: https://www.figma.com/file/LsVB4KlIMXD4Z1FfB8KyuU/FlutterKaigi-Web-2023
95 |
96 | [Flutter beta channel]: https://github.com/flutter/flutter/wiki/Roadmap#releases
97 |
98 | [Install fvm]: https://fvm.app/docs/getting_started/installation
99 |
100 | [VSCode]: https://code.visualstudio.com/
101 |
102 | [Android Studio]: https://developer.android.com/studio
103 |
104 | [fvm document]: https://fvm.app/docs/getting_started/configuration#android-studio
105 |
106 | [CONTRIBUTING.md]: ./CONTRIBUTING.md
107 |
108 | [CONTRIBUTING.ja.md]: ./CONTRIBUTING.ja.md
109 |
110 | [Flutter]: https://flutter.dev/
111 |
112 | [Flutter Web]: https://docs.flutter.dev/deployment/web
113 |
114 | [GitHub Pages]: https://docs.github.com/ja/pages/getting-started-with-github-pages/about-github-pages
115 |
116 | [Codemagic Static Pages]: https://docs.codemagic.io/flutter-publishing/publishing-to-codemagic-static-pages/
117 |
118 | [Contributors]: https://github.com/FlutterKaigi/2023/graphs/contributors
119 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: all_lint_rules.yaml
2 |
3 | linter:
4 | rules:
5 | # Conflicts with `omit_local_variable_types`
6 | # Prefer defining types for function expression parameters
7 | avoid_types_on_closure_parameters: false
8 | always_specify_types: false
9 | # Conflicts with `prefer_single_quotes`
10 | # Single quotes are more readable than double quotes
11 | prefer_double_quotes: false
12 | # Conflicts with `avoid_final_parameters`
13 | # If it's clearly "final", it's ok to omit it
14 | prefer_final_parameters: false
15 | # Conflicts with `always_use_package_imports`
16 | # Prohibit the use of relative paths in import statements
17 | prefer_relative_imports: false
18 | # Conflicts with `prefer_final_fields`
19 | # Prefer using `final` for declaring fields if possible
20 | unnecessary_final: false
21 |
22 | # Allow to define public members without docs
23 | public_member_api_docs: false
24 | # Allow to create TODO without defining Github username and issue URL
25 | flutter_style_todos: false
26 | # Allow to define short members without using `=>`
27 | prefer_expression_function_bodies: false
28 | # It's better to use `dynamic varName` instead of simply `varName`
29 | avoid_annotating_with_dynamic: false
30 | # Allow to define public parameters without diagostic properties
31 | diagnostic_describe_all_properties: false
32 |
33 | analyzer:
34 | language:
35 | # Increase safety as much as possible
36 | strict-casts: true
37 | strict-inference: true
38 | strict-raw-types: true
39 | errors:
40 | # https://github.com/dart-lang/sdk/issues/33550#issuecomment-398948710
41 | # allow for asset_does_not_exist
42 | unrecognized_error_code: ignore
43 | # ignore this in pubspec.yaml
44 | asset_does_not_exist: ignore
45 |
46 | # Importing all_lint_rules causes conflict error. Ignore it.
47 | included_file_warning: ignore
48 |
49 | # https://github.com/rrousselGit/freezed#disabling-invalid_annotation_target-warning-and-warning-in-generates-files
50 | invalid_annotation_target: ignore
51 |
--------------------------------------------------------------------------------
/assets/app_links/app_store.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterKaigi/2023/5add6883ca94e1257a296e47befd6aded2338489/assets/app_links/app_store.png
--------------------------------------------------------------------------------
/assets/app_links/google_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterKaigi/2023/5add6883ca94e1257a296e47befd6aded2338489/assets/app_links/google_play.png
--------------------------------------------------------------------------------
/assets/flutterkaigi_logo_shadowed.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/assets/flutterkaigi_logo_union.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/assets/icons/copy.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icons/dot.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icons/github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icons/medium.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icons/note.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/assets/icons/qiita.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/icons/zenn.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/assets/sponsors/bitkey.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/assets/sponsors/cyberagent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterKaigi/2023/5add6883ca94e1257a296e47befd6aded2338489/assets/sponsors/cyberagent.png
--------------------------------------------------------------------------------
/assets/sponsors/enechain.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/assets/sponsors/harmo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterKaigi/2023/5add6883ca94e1257a296e47befd6aded2338489/assets/sponsors/harmo.png
--------------------------------------------------------------------------------
/assets/sponsors/pioneer.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/assets/sponsors/recruit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterKaigi/2023/5add6883ca94e1257a296e47befd6aded2338489/assets/sponsors/recruit.png
--------------------------------------------------------------------------------
/assets/sponsors/tenx.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/assets/sponsors/yumemi.svg:
--------------------------------------------------------------------------------
1 |
42 |
--------------------------------------------------------------------------------
/build.yaml:
--------------------------------------------------------------------------------
1 | targets:
2 | $default:
3 | builders:
4 | source_gen|combining_builder:
5 | options:
6 | ignore_for_file:
7 | - type=lint
8 | - duplicate_ignore
9 | json_serializable:
10 | options:
11 | checked: true
12 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "build/web",
4 | "rewrites": [
5 | {
6 | "source": "**",
7 | "destination": "/index.html"
8 | }
9 | ],
10 | "ignore": [
11 | "firebase.json",
12 | "**/.* !**/.env",
13 | "**/node_modules/**"
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/flavor/mock.json:
--------------------------------------------------------------------------------
1 | {
2 | "FLAVOR": "mock"
3 | }
4 |
--------------------------------------------------------------------------------
/flavor/production.json:
--------------------------------------------------------------------------------
1 | {
2 | "FLAVOR": "production"
3 | }
4 |
--------------------------------------------------------------------------------
/l10n.yaml:
--------------------------------------------------------------------------------
1 | arb-dir: lib/core/l10n
2 | template-arb-file: app_ja.arb
3 | output-localization-file: app_localizations.dart
4 |
--------------------------------------------------------------------------------
/lib/app/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/app/config.dart';
2 | import 'package:confwebsite2023/app/router/router.dart';
3 | import 'package:confwebsite2023/core/theme.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_gen/gen_l10n/app_localizations.dart';
6 | import 'package:hooks_riverpod/hooks_riverpod.dart';
7 |
8 | class App extends ConsumerWidget {
9 | const App({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context, WidgetRef ref) {
13 | final config = ref.watch(configProvider);
14 | final router = ref.watch(routerProvider);
15 | return MaterialApp.router(
16 | title: config.appTitle,
17 | theme: lightTheme, // Specified but not used
18 | darkTheme: darkTheme,
19 | themeMode: ThemeMode.dark,
20 | localizationsDelegates: AppLocalizations.localizationsDelegates,
21 | supportedLocales: AppLocalizations.supportedLocales,
22 | routerConfig: router,
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/app/config.dart:
--------------------------------------------------------------------------------
1 | // `String.fromEnvironment` の乱用を防ぐために、このファイルのみ警告を無効にして
2 | // プロジェクト全体では無効にしていない
3 | // ignore_for_file: do_not_use_environment
4 |
5 | import 'package:riverpod_annotation/riverpod_annotation.dart';
6 |
7 | part 'config.g.dart';
8 |
9 | @riverpod
10 | Config config(ConfigRef ref) {
11 | throw UnimplementedError();
12 | }
13 |
14 | class Config {
15 | factory Config() {
16 | final flavor = Flavor.values.byName(
17 | const String.fromEnvironment('FLAVOR'),
18 | );
19 | return Config._(
20 | flavor: flavor,
21 | );
22 | }
23 |
24 | const Config._({
25 | required Flavor flavor,
26 | }) : _flavor = flavor;
27 |
28 | final Flavor _flavor;
29 |
30 | String get appTitle {
31 | const title = 'FlutterKaigi 2023';
32 | // 本番環境以外で動作していることが分かるように、アプリタイトルの先頭に Flavor 名を追加する
33 | return switch (_flavor) {
34 | Flavor.production => title,
35 | _ => '(${_flavor.name}) $title',
36 | };
37 | }
38 |
39 | bool get isMock => _flavor == Flavor.mock;
40 | }
41 |
42 | enum Flavor {
43 | mock,
44 | production,
45 | }
46 |
--------------------------------------------------------------------------------
/lib/app/config.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'config.dart';
6 |
7 | // **************************************************************************
8 | // RiverpodGenerator
9 | // **************************************************************************
10 |
11 | String _$configHash() => r'fae3f69ee168d0e03e2f32e84a85b59793392563';
12 |
13 | /// See also [config].
14 | @ProviderFor(config)
15 | final configProvider = AutoDisposeProvider.internal(
16 | config,
17 | name: r'configProvider',
18 | debugGetCreateSourceHash:
19 | const bool.fromEnvironment('dart.vm.product') ? null : _$configHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef ConfigRef = AutoDisposeProviderRef;
25 | // ignore_for_file: type=lint
26 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member
27 |
--------------------------------------------------------------------------------
/lib/app/session_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:math';
3 |
4 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
5 | import 'package:confwebsite2023/core/theme.dart';
6 | import 'package:confwebsite2023/features/footer/ui/footer.dart';
7 | import 'package:confwebsite2023/features/header/ui/header_widget.dart';
8 | import 'package:confwebsite2023/features/session/ui/detail/session_detail.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_hooks/flutter_hooks.dart';
11 | import 'package:hooks_riverpod/hooks_riverpod.dart';
12 | import 'package:js/js_util.dart' as js_util;
13 |
14 | class SessionPage extends HookConsumerWidget {
15 | const SessionPage({super.key});
16 |
17 | @override
18 | Widget build(BuildContext context, WidgetRef ref) {
19 | useEffect(() {
20 | unawaited(
21 | WidgetsBinding.instance.endOfFrame.then((_) {
22 | js_util.callMethod(js_util.globalThis, '_show', []);
23 | }),
24 | );
25 | return null;
26 | });
27 |
28 | return Scaffold(
29 | backgroundColor: baselineColorScheme.ref.secondary.secondary10,
30 | body: const _MainPageBody(),
31 | appBar: const LogoOnlyHeaderBar(),
32 | extendBodyBehindAppBar: true,
33 | );
34 | }
35 | }
36 |
37 | class _MainPageBody extends StatelessWidget {
38 | const _MainPageBody();
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | final width = MediaQuery.sizeOf(context).width;
43 | final largeScreenSize = ResponsiveWidget.largeScreenSize.toDouble();
44 | final horizontal = max(16, (width - largeScreenSize) / 4.0);
45 | final padding = EdgeInsets.symmetric(
46 | horizontal: horizontal,
47 | );
48 |
49 | return Stack(
50 | children: [
51 | const SizedBox(
52 | width: double.infinity,
53 | height: 800,
54 | child: DecoratedBox(
55 | decoration: BoxDecoration(
56 | gradient: LinearGradient(
57 | begin: Alignment.topCenter,
58 | end: Alignment.bottomCenter,
59 | colors: [
60 | Color(0xFF602678),
61 | Color(0x004B0082),
62 | ],
63 | ),
64 | ),
65 | ),
66 | ),
67 | CustomScrollView(
68 | slivers: [
69 | const SliverToBoxAdapter(
70 | child: Spaces.vertical_80,
71 | ),
72 | _Sliver(
73 | padding: padding,
74 | child: const SessionDetail(),
75 | ),
76 | const SliverToBoxAdapter(
77 | child: Spaces.vertical_200,
78 | ),
79 | const SliverToBoxAdapter(
80 | child: Footer(),
81 | ),
82 | ],
83 | ),
84 | ],
85 | );
86 | }
87 | }
88 |
89 | class _Sliver extends StatelessWidget {
90 | const _Sliver({
91 | required this.child,
92 | required this.padding,
93 | });
94 |
95 | final Widget child;
96 | final EdgeInsets padding;
97 |
98 | @override
99 | Widget build(BuildContext context) {
100 | return SliverPadding(
101 | padding: padding,
102 | sliver: SliverToBoxAdapter(
103 | child: child,
104 | ),
105 | );
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/lib/app/sponsor_page.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:math';
3 |
4 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
5 | import 'package:confwebsite2023/core/theme.dart';
6 | import 'package:confwebsite2023/features/footer/ui/footer.dart';
7 | import 'package:confwebsite2023/features/header/ui/header_widget.dart';
8 | import 'package:confwebsite2023/features/sponsor/ui/detail/sponsor_detail.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_hooks/flutter_hooks.dart';
11 | import 'package:hooks_riverpod/hooks_riverpod.dart';
12 | import 'package:js/js_util.dart' as js_util;
13 | import 'package:riverpod_annotation/riverpod_annotation.dart';
14 |
15 | part 'sponsor_page.g.dart';
16 |
17 | @riverpod
18 | String sponsorName(SponsorNameRef ref) => throw UnimplementedError();
19 |
20 | final class SponsorPage extends HookConsumerWidget {
21 | const SponsorPage({super.key});
22 |
23 | @override
24 | Widget build(BuildContext context, WidgetRef ref) {
25 | useEffect(() {
26 | unawaited(
27 | WidgetsBinding.instance.endOfFrame.then((_) {
28 | js_util.callMethod(js_util.globalThis, '_show', []);
29 | }),
30 | );
31 | return null;
32 | });
33 |
34 | return Scaffold(
35 | backgroundColor: baselineColorScheme.ref.secondary.secondary10,
36 | body: const _MainPageBody(),
37 | appBar: const LogoOnlyHeaderBar(),
38 | extendBodyBehindAppBar: true,
39 | );
40 | }
41 | }
42 |
43 | class _MainPageBody extends StatelessWidget {
44 | const _MainPageBody();
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | final width = MediaQuery.sizeOf(context).width;
49 | final largeScreenSize = ResponsiveWidget.largeScreenSize.toDouble();
50 | final horizontal = max(16, (width - largeScreenSize) / 4.0);
51 | final padding = EdgeInsets.symmetric(
52 | horizontal: horizontal,
53 | );
54 |
55 | return Stack(
56 | children: [
57 | const SizedBox(
58 | width: double.infinity,
59 | height: 800,
60 | child: DecoratedBox(
61 | decoration: BoxDecoration(
62 | gradient: LinearGradient(
63 | begin: Alignment.topCenter,
64 | end: Alignment.bottomCenter,
65 | colors: [
66 | Color(0xFF602678),
67 | Color(0x004B0082),
68 | ],
69 | ),
70 | ),
71 | ),
72 | ),
73 | CustomScrollView(
74 | slivers: [
75 | const SliverToBoxAdapter(
76 | child: Spaces.vertical_80,
77 | ),
78 | _Sliver(
79 | padding: padding,
80 | child: const SponsorDetail(),
81 | ),
82 | const SliverToBoxAdapter(
83 | child: Spaces.vertical_200,
84 | ),
85 | const SliverToBoxAdapter(
86 | child: Footer(),
87 | ),
88 | ],
89 | ),
90 | ],
91 | );
92 | }
93 | }
94 |
95 | class _Sliver extends StatelessWidget {
96 | const _Sliver({
97 | required this.child,
98 | required this.padding,
99 | });
100 |
101 | final Widget child;
102 | final EdgeInsets padding;
103 |
104 | @override
105 | Widget build(BuildContext context) {
106 | return SliverPadding(
107 | padding: padding,
108 | sliver: SliverToBoxAdapter(
109 | child: child,
110 | ),
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/lib/app/sponsor_page.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'sponsor_page.dart';
6 |
7 | // **************************************************************************
8 | // RiverpodGenerator
9 | // **************************************************************************
10 |
11 | String _$sponsorNameHash() => r'1c8d0d7053840d78758644034bfd4b79ce48cb2f';
12 |
13 | /// See also [sponsorName].
14 | @ProviderFor(sponsorName)
15 | final sponsorNameProvider = AutoDisposeProvider.internal(
16 | sponsorName,
17 | name: r'sponsorNameProvider',
18 | debugGetCreateSourceHash:
19 | const bool.fromEnvironment('dart.vm.product') ? null : _$sponsorNameHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef SponsorNameRef = AutoDisposeProviderRef;
25 | // ignore_for_file: type=lint
26 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member
27 |
--------------------------------------------------------------------------------
/lib/core/components/copy_url_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_svg/flutter_svg.dart';
5 |
6 | final class CopyUrlButton extends StatelessWidget {
7 | const CopyUrlButton({super.key});
8 |
9 | @override
10 | Widget build(BuildContext context) => FilledButton.icon(
11 | onPressed: () async {
12 | final data = ClipboardData(text: Uri.base.toString());
13 | await Clipboard.setData(data);
14 |
15 | if (!context.mounted) {
16 | return;
17 | }
18 | ScaffoldMessenger.of(context).showSnackBar(
19 | const SnackBar(
20 | content: Text('URLをコピーしました'),
21 | ),
22 | );
23 | },
24 | icon: SvgPicture.asset(
25 | Assets.icons.copy,
26 | width: 18,
27 | height: 18,
28 | colorFilter: ColorFilter.mode(
29 | Theme.of(context).colorScheme.onPrimary,
30 | BlendMode.srcIn,
31 | ),
32 | ),
33 | label: const Text('URLをコピー'),
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/lib/core/components/finish_snack_bar.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class FinishSnackBar extends StatelessWidget {
4 | const FinishSnackBar({super.key});
5 |
6 | @override
7 | Widget build(BuildContext context) {
8 | final theme = Theme.of(context);
9 | final textTheme = theme.textTheme;
10 | return DecoratedBox(
11 | decoration: BoxDecoration(
12 | color: const Color(0xFFE6E1E5),
13 | borderRadius: BorderRadius.circular(4),
14 | ),
15 | child: Padding(
16 | padding: const EdgeInsets.all(16),
17 | child: Text(
18 | 'FlutterKaigi2023は終了しました。たくさんの方にご参加いただきありがとうございました。',
19 | style: textTheme.bodyMedium!.copyWith(
20 | color: const Color(0xFF49454F),
21 | ),
22 | ),
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/core/components/flutter_kaigi_logo.dart:
--------------------------------------------------------------------------------
1 | // ignore: avoid_web_libraries_in_flutter
2 | import 'dart:html';
3 | import 'dart:ui_web' as ui;
4 |
5 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
6 | import 'package:confwebsite2023/core/theme.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_svg/svg.dart';
9 |
10 | class FlutterKaigiLogo extends StatelessWidget {
11 | const FlutterKaigiLogo({
12 | required this.style,
13 | this.iconColor = Colors.white,
14 | this.textStyle = const TextStyle(
15 | fontSize: 24,
16 | fontWeight: FontWeight.bold,
17 | ),
18 | this.showGradient,
19 | this.size,
20 | super.key,
21 | });
22 | final FlutterKaigiLogoStyle style;
23 |
24 | final TextStyle textStyle;
25 | final Color iconColor;
26 |
27 | /// The size of the logo in logical pixels.
28 | ///
29 | /// Defaults to the current [IconTheme] size, if any. If there is no
30 | /// [IconTheme], or it does not specify an explicit size, then it defaults to
31 | /// 24.0.
32 | final double? size;
33 |
34 | /// If true, this widget will show gradient behind the logo.
35 | final bool? showGradient;
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | final showGradient = this.showGradient ?? style.showGraident;
40 |
41 | final iconTheme = IconTheme.of(context);
42 | final iconSize = size ?? iconTheme.size ?? 24;
43 |
44 | final baseIcon = SvgPicture.asset(
45 | Assets.flutterkaigiLogoUnion,
46 | width: iconSize,
47 | height: iconSize,
48 | colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
49 | );
50 | final shadowedIcon = _FlutterKaigiShadowedLogo(size: iconSize);
51 | return switch (style) {
52 | FlutterKaigiLogoStyle.markOnly when showGradient => shadowedIcon,
53 | FlutterKaigiLogoStyle.markOnly => baseIcon,
54 | FlutterKaigiLogoStyle.horizontal => Row(
55 | mainAxisSize: MainAxisSize.min,
56 | children: [
57 | baseIcon,
58 | Spaces.horizontal_10,
59 | Text(
60 | 'FlutterKaigi 2023',
61 | style: textStyle,
62 | ),
63 | ],
64 | ),
65 | FlutterKaigiLogoStyle.stacked => Column(
66 | mainAxisSize: MainAxisSize.min,
67 | children: [
68 | baseIcon,
69 | Spaces.vertical_10,
70 | Text(
71 | 'FlutterKaigi 2023',
72 | style: textStyle,
73 | ),
74 | ],
75 | ),
76 | };
77 | }
78 | }
79 |
80 | class _FlutterKaigiShadowedLogo extends StatelessWidget {
81 | const _FlutterKaigiShadowedLogo({
82 | required this.size,
83 | });
84 |
85 | final double size;
86 |
87 | @override
88 | Widget build(BuildContext context) {
89 | const viewType = 'flutterkaigi-logo-shadow';
90 | const imageUrl = 'assets/${Assets.flutterkaigiLogoShadowed}';
91 | ui.platformViewRegistry.registerViewFactory(
92 | viewType,
93 | (int viewType) => ImageElement()..src = imageUrl,
94 | );
95 | return SizedBox(
96 | height: size,
97 | width: size,
98 | child: const HtmlElementView(
99 | viewType: viewType,
100 | ),
101 | );
102 | }
103 | }
104 |
105 | /// Possible ways to draw Flutter Kaigi's logo.
106 | /// ref: `FlutterLogoStyle`
107 | enum FlutterKaigiLogoStyle {
108 | /// Show only Flutter's logo, not the "Flutter" label.
109 | markOnly(showGraident: true),
110 |
111 | /// Show Flutter's logo on the left, and the "Flutter" label to its right.
112 | horizontal(showGraident: false),
113 |
114 | /// Show Flutter's logo above the "Flutter" label.
115 | stacked(showGraident: false),
116 | ;
117 |
118 | const FlutterKaigiLogoStyle({required this.showGraident});
119 | final bool showGraident;
120 | }
121 |
--------------------------------------------------------------------------------
/lib/core/components/left_filled_icon_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/theme.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class LeftFilledIconButton extends StatelessWidget {
5 | const LeftFilledIconButton({
6 | required this.onPressed,
7 | required this.buttonTitle,
8 | required this.icon,
9 | this.width,
10 | super.key,
11 | });
12 |
13 | final void Function() onPressed;
14 | final String buttonTitle;
15 | final IconData icon;
16 | final double? width;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | final theme = Theme.of(context);
21 | final textTheme = theme.textTheme;
22 | final colorScheme = theme.colorScheme;
23 | return SizedBox(
24 | width: width,
25 | height: 40,
26 | child: ElevatedButton(
27 | style: ElevatedButton.styleFrom(
28 | backgroundColor: colorScheme.primary,
29 | padding: const EdgeInsets.symmetric(
30 | horizontal: 16,
31 | vertical: 8,
32 | ),
33 | ),
34 | onPressed: onPressed,
35 | child: Row(
36 | mainAxisAlignment: MainAxisAlignment.center,
37 | mainAxisSize: MainAxisSize.min,
38 | children: [
39 | Spaces.horizontal_8,
40 | Text(
41 | buttonTitle,
42 | style: textTheme.labelLarge?.copyWith(
43 | color: colorScheme.onPrimary,
44 | ),
45 | ),
46 | Spaces.horizontal_8,
47 | Icon(
48 | icon,
49 | size: 18,
50 | color: colorScheme.onPrimary,
51 | ),
52 | ],
53 | ),
54 | ),
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/core/components/list_bullet.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/theme.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// [ListBullet] is a primary color circle mark.
5 | final class ListBullet extends StatelessWidget {
6 | const ListBullet({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) => Container(
10 | width: 8,
11 | height: 8,
12 | decoration: BoxDecoration(
13 | color: baselineColorScheme.ref.primary.primary40,
14 | shape: BoxShape.circle,
15 | ),
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/lib/core/components/profile_image.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:transparent_image/transparent_image.dart';
3 |
4 | class ProfileImage extends StatelessWidget {
5 | const ProfileImage({
6 | required this.imageUrl,
7 | required this.size,
8 | super.key,
9 | });
10 | final String imageUrl;
11 | final double size;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return SizedBox(
16 | height: size,
17 | width: size,
18 | child: FittedBox(
19 | child: ClipOval(
20 | child: FadeInImage(
21 | fit: BoxFit.cover,
22 | image: NetworkImage(imageUrl),
23 | placeholder: MemoryImage(kTransparentImage),
24 | imageErrorBuilder: (_, __, ___) => const FittedBox(
25 | child: Icon(
26 | Icons.error,
27 | ),
28 | ),
29 | ),
30 | ),
31 | ),
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/core/components/responsive_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | /// A widget that displays a different widget based on the screen size.
4 | /// If [mediumWidget] and [smallWidget] are not provided, [largeWidget] is used
5 | /// If [smallWidget] is not provided, [mediumWidget] is used
6 | /// If [mediumWidget] is not provided, [largeWidget] is used
7 | class ResponsiveWidget extends StatelessWidget {
8 | const ResponsiveWidget({
9 | required this.largeWidget,
10 | this.mediumWidget,
11 | this.smallWidget,
12 | super.key,
13 | });
14 |
15 | final Widget largeWidget;
16 | final Widget? mediumWidget;
17 | final Widget? smallWidget;
18 |
19 | /// Threshold for large screen
20 | static const int largeScreenSize = 1200;
21 |
22 | /// Threshold for medium screen
23 | static const int mediumScreenSize = 800;
24 |
25 | /// Returns the screen size type based on the screen width
26 | static ScreenSizeType getScreenSizeType(BuildContext context) {
27 | final screenWidth = MediaQuery.sizeOf(context).width;
28 | return switch (screenWidth) {
29 | >= largeScreenSize => ScreenSizeType.large,
30 | >= mediumScreenSize => ScreenSizeType.medium,
31 | _ => ScreenSizeType.small,
32 | };
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | final type = getScreenSizeType(context);
38 | return switch (type) {
39 | ScreenSizeType.small when smallWidget != null => smallWidget!,
40 | ScreenSizeType.small when mediumWidget != null => mediumWidget!,
41 | ScreenSizeType.medium when mediumWidget != null => mediumWidget!,
42 | _ => largeWidget,
43 | };
44 | }
45 | }
46 |
47 | enum ScreenSizeType {
48 | small,
49 | medium,
50 | large,
51 | ;
52 | }
53 |
--------------------------------------------------------------------------------
/lib/core/components/section_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | enum SectionHeaderAlignment {
4 | left,
5 | center,
6 | }
7 |
8 | /// A section header with a gradient.
9 | final class SectionHeader extends StatelessWidget {
10 | const SectionHeader.leftAlignment({
11 | required this.text,
12 | required this.style,
13 | required this.gradient,
14 | super.key,
15 | }) : _alignment = SectionHeaderAlignment.left;
16 |
17 | const SectionHeader.centerAlignment({
18 | required this.text,
19 | required this.style,
20 | required this.gradient,
21 | super.key,
22 | }) : _alignment = SectionHeaderAlignment.center;
23 |
24 | /// ブラーのぼかし範囲
25 | static const double _blurRadius = 20;
26 |
27 | final SectionHeaderAlignment _alignment;
28 |
29 | /// The text to display.
30 | final String text;
31 |
32 | final TextStyle style;
33 |
34 | final Gradient gradient;
35 |
36 | @override
37 | Widget build(BuildContext context) {
38 | final component = RepaintBoundary(
39 | child: ShaderMask(
40 | shaderCallback: gradient.createShader,
41 | blendMode: BlendMode.srcIn,
42 | child: Padding(
43 | // NOTE: Text Widget の描画範囲から外れて文字やブラーが見切れてしまうため、現状は左右に余白を設けている
44 | padding: const EdgeInsets.all(_blurRadius),
45 | child: Text(
46 | text,
47 | style: style.copyWith(
48 | color: Colors.white,
49 | ),
50 | ),
51 | ),
52 | ),
53 | );
54 |
55 | switch (_alignment) {
56 | case SectionHeaderAlignment.left:
57 | return Transform.translate(
58 | // NOTE: ブラーのぼかし範囲を考慮してオフセットを設定する
59 | offset: const Offset(-SectionHeader._blurRadius, 0),
60 | child: Align(
61 | alignment: Alignment.centerLeft,
62 | child: component,
63 | ),
64 | );
65 | case SectionHeaderAlignment.center:
66 | return Align(
67 | child: component,
68 | );
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/core/components/social_share.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/copy_url_button.dart';
2 | import 'package:confwebsite2023/core/components/tweet_button.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | final class SocialShare extends StatelessWidget {
6 | const SocialShare({
7 | this.alignment = WrapAlignment.end,
8 | super.key,
9 | });
10 |
11 | final WrapAlignment alignment;
12 |
13 | @override
14 | Widget build(BuildContext context) => Wrap(
15 | alignment: alignment,
16 | spacing: 10,
17 | runSpacing: 10,
18 | children: const [
19 | TweetButton(),
20 | CopyUrlButton(),
21 | ],
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/lib/core/components/tweet_button.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_svg/flutter_svg.dart';
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | final class TweetButton extends StatelessWidget {
9 | const TweetButton({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return FilledButton.icon(
14 | onPressed: () async {
15 | final url = Uri.parse(
16 | 'https://twitter.com/share?url=${Uri.base}&hashtags=flutterkaigi&via=FlutterKaigi',
17 | );
18 | unawaited(launchUrl(url));
19 | },
20 | icon: SvgPicture.asset(
21 | Assets.icons.twitter,
22 | width: 18,
23 | height: 18,
24 | colorFilter: ColorFilter.mode(
25 | Theme.of(context).colorScheme.onPrimary,
26 | BlendMode.srcIn,
27 | ),
28 | ),
29 | label: const Text('ツイートする'),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/core/foundation/duration_ex.dart:
--------------------------------------------------------------------------------
1 | extension DurationEx on Duration {
2 | int get clockedDays => inDays;
3 | int get clockedHours => inHours % 24;
4 | int get clockedMinutes => inMinutes % 60;
5 | int get clockedSeconds => inSeconds % 60;
6 | }
7 |
--------------------------------------------------------------------------------
/lib/core/foundation/iterable_ex.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart';
2 |
3 | enum InsertLocation {
4 | around,
5 | between,
6 | }
7 |
8 | extension IterableEx on Iterable {
9 | List insertingEach(
10 | E Function() toElement, {
11 | InsertLocation location = InsertLocation.between,
12 | }) {
13 | return insertingListEach(() => [toElement()], location: location);
14 | }
15 |
16 | List insertingListEach(
17 | List Function() toElements, {
18 | InsertLocation location = InsertLocation.between,
19 | }) {
20 | switch (location) {
21 | case InsertLocation.around:
22 | final lastIndex = length - 1;
23 | return foldIndexed([], (index, acc, item) {
24 | final base = [...acc, item];
25 | if (index == 0) {
26 | return [...toElements(), ...base];
27 | } else if (index == lastIndex) {
28 | return [...base, ...toElements()];
29 | } else {
30 | return base;
31 | }
32 | });
33 |
34 | case InsertLocation.between:
35 | final lastIndex = length - 1;
36 | return foldIndexed([], (index, acc, item) {
37 | final base = [...acc, item];
38 | if (index == lastIndex) {
39 | return base;
40 | } else {
41 | return [...base, ...toElements()];
42 | }
43 | });
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/core/l10n/app_en.arb:
--------------------------------------------------------------------------------
1 | {
2 | "executive_committee": "Executive Committee"
3 | }
4 |
--------------------------------------------------------------------------------
/lib/core/l10n/app_ja.arb:
--------------------------------------------------------------------------------
1 | {
2 | "executive_committee": "実行委員"
3 | }
4 |
--------------------------------------------------------------------------------
/lib/core/theme.dart:
--------------------------------------------------------------------------------
1 | export 'theme/app_text_style.dart';
2 | export 'theme/app_theme.dart';
3 | export 'theme/baseline_color_scheme.dart';
4 | export 'theme/dimension.dart';
5 | export 'theme/gradient.dart';
6 |
--------------------------------------------------------------------------------
/lib/core/theme/app_text_style.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | import 'package:confwebsite2023/core/theme/gradient.dart';
4 | import 'package:google_fonts/google_fonts.dart';
5 |
6 | class AppTextStyle {
7 | const AppTextStyle._();
8 |
9 | static final _shadowHeading1 = Shadow(
10 | color: GradientConstant.accent.primary.colors.first.withOpacity(0.25),
11 | blurRadius: 20,
12 | offset: const Offset(0, 2),
13 | );
14 |
15 | static final _shadowHeading2 = Shadow(
16 | color: GradientConstant.accent.secondary.colors.last.withOpacity(0.25),
17 | blurRadius: 10,
18 | offset: const Offset(0, 2),
19 | );
20 |
21 | static final _baseHeading = GoogleFonts.poppins(
22 | fontWeight: FontWeight.w700,
23 | fontStyle: FontStyle.italic,
24 | height: 1.1,
25 | );
26 |
27 | static final pcHeading1 = _baseHeading.copyWith(
28 | fontSize: 60, // 80px * 0.75 -> 60pt
29 | shadows: [
30 | _shadowHeading1,
31 | ],
32 | );
33 |
34 | static final pcHeading2 = _baseHeading.copyWith(
35 | fontSize: 36, // 48px * 0.75-> 36pt
36 | shadows: [
37 | _shadowHeading2,
38 | ],
39 | );
40 |
41 | static final spHeading1 = _baseHeading.copyWith(
42 | fontSize: 36, // 48px * 0.75-> 36pt
43 | shadows: [
44 | _shadowHeading1,
45 | ],
46 | );
47 |
48 | static final spHeading2 = _baseHeading.copyWith(
49 | fontSize: 24, // 32px * 0.75-> 24pt
50 | shadows: [
51 | _shadowHeading2,
52 | ],
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/lib/core/theme/app_theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:google_fonts/google_fonts.dart';
3 |
4 | ThemeData get lightTheme => _builder(_lightColorScheme);
5 |
6 | ThemeData get darkTheme => _builder(_darkColorScheme);
7 |
8 | ThemeData _builder(ColorScheme scheme) {
9 | final theme = ThemeData.from(
10 | colorScheme: scheme,
11 | useMaterial3: true,
12 | );
13 | final robotoTextTheme = GoogleFonts.robotoTextTheme(theme.textTheme);
14 | return theme.copyWith(
15 | textTheme: robotoTextTheme,
16 | );
17 | }
18 |
19 | /// Generated using the Material Theme Builder
20 | /// See https://m3.material.io/theme-builder
21 | const _lightColorScheme = ColorScheme(
22 | brightness: Brightness.light,
23 | primary: Color(0xFF6750A4),
24 | onPrimary: Color(0xFFFFFFFF),
25 | primaryContainer: Color(0xFFEADDFF),
26 | onPrimaryContainer: Color(0xFF21005D),
27 | secondary: Color(0xFF625B71),
28 | onSecondary: Color(0xFFFFFFFF),
29 | secondaryContainer: Color(0xFFE8DEF8),
30 | onSecondaryContainer: Color(0xFF1D192B),
31 | tertiary: Color(0xFF7D5260),
32 | onTertiary: Color(0xFFFFFFFF),
33 | tertiaryContainer: Color(0xFFFFD8E4),
34 | onTertiaryContainer: Color(0xFF31111D),
35 | error: Color(0xFFB3261E),
36 | onError: Color(0xFFFFFFFF),
37 | errorContainer: Color(0xFFF9DEDC),
38 | onErrorContainer: Color(0xFF410E0B),
39 | outline: Color(0xFF79747E),
40 | background: Color(0xFFFEF7FF),
41 | onBackground: Color(0xFF1D1B20),
42 | surface: Color(0xFFFEF7FF),
43 | onSurface: Color(0xFF1D1B20),
44 | surfaceVariant: Color(0xFFE7E0EC),
45 | onSurfaceVariant: Color(0xFF49454F),
46 | inverseSurface: Color(0xFF322F35),
47 | onInverseSurface: Color(0xFFF5EFF7),
48 | inversePrimary: Color(0xFFD0BCFF),
49 | shadow: Color(0xFF000000),
50 | surfaceTint: Color(0xFF6750A4),
51 | outlineVariant: Color(0xFFCAC4D0),
52 | scrim: Color(0xFF000000),
53 | );
54 | const _darkColorScheme = ColorScheme(
55 | brightness: Brightness.dark,
56 | primary: Color(0xFFD0BCFF),
57 | onPrimary: Color(0xFF381E72),
58 | primaryContainer: Color(0xFF4F378B),
59 | onPrimaryContainer: Color(0xFFEADDFF),
60 | secondary: Color(0xFFCCC2DC),
61 | onSecondary: Color(0xFF332D41),
62 | secondaryContainer: Color(0xFF4A4458),
63 | onSecondaryContainer: Color(0xFFE8DEF8),
64 | tertiary: Color(0xFFEFB8C8),
65 | onTertiary: Color(0xFF492532),
66 | tertiaryContainer: Color(0xFF633B48),
67 | onTertiaryContainer: Color(0xFFFFD8E4),
68 | error: Color(0xFFF2B8B5),
69 | onError: Color(0xFF601410),
70 | errorContainer: Color(0xFF8C1D18),
71 | onErrorContainer: Color(0xFFF9DEDC),
72 | outline: Color(0xFF938F99),
73 | background: Color(0xFF141218),
74 | onBackground: Color(0xFFE6E0E9),
75 | surface: Color(0xFF141218),
76 | onSurface: Color(0xFFE6E0E9),
77 | surfaceVariant: Color(0xFF49454F),
78 | onSurfaceVariant: Color(0xFFCAC4D0),
79 | inverseSurface: Color(0xFFE6E0E9),
80 | onInverseSurface: Color(0xFF322F35),
81 | inversePrimary: Color(0xFF6750A4),
82 | shadow: Color(0xFF000000),
83 | surfaceTint: Color(0xFFD0BCFF),
84 | outlineVariant: Color(0xFF49454F),
85 | scrim: Color(0xFF000000),
86 | );
87 |
--------------------------------------------------------------------------------
/lib/core/theme/dimension.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class Spaces {
4 | const Spaces._();
5 |
6 | static const SizedBox horizontal_4 = SizedBox(width: 4);
7 | static const SizedBox horizontal_8 = SizedBox(width: 8);
8 | static const SizedBox horizontal_10 = SizedBox(width: 10);
9 | static const SizedBox horizontal_12 = SizedBox(width: 12);
10 | static const SizedBox horizontal_16 = SizedBox(width: 16);
11 | static const SizedBox horizontal_20 = SizedBox(width: 20);
12 | static const SizedBox horizontal_40 = SizedBox(width: 40);
13 | static const SizedBox horizontal_60 = SizedBox(width: 60);
14 |
15 | static const SizedBox vertical_5 = SizedBox(height: 5);
16 | static const SizedBox vertical_8 = SizedBox(height: 8);
17 | static const SizedBox vertical_10 = SizedBox(height: 10);
18 | static const SizedBox vertical_16 = SizedBox(height: 16);
19 | static const SizedBox vertical_20 = SizedBox(height: 20);
20 | static const SizedBox vertical_24 = SizedBox(height: 24);
21 | static const SizedBox vertical_28 = SizedBox(height: 28);
22 | static const SizedBox vertical_30 = SizedBox(height: 30);
23 | static const SizedBox vertical_36 = SizedBox(height: 36);
24 | static const SizedBox vertical_40 = SizedBox(height: 40);
25 | static const SizedBox vertical_80 = SizedBox(height: 80);
26 | static const SizedBox vertical_82 = SizedBox(height: 82);
27 | static const SizedBox vertical_100 = SizedBox(height: 100);
28 | static const SizedBox vertical_200 = SizedBox(height: 200);
29 | }
30 |
--------------------------------------------------------------------------------
/lib/core/theme/gradient.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: library_private_types_in_public_api
2 |
3 | import 'package:flutter/painting.dart';
4 |
5 | class GradientConstant {
6 | GradientConstant._();
7 |
8 | static const accent = _Accent();
9 | static const sponsor = _Sponsor();
10 | }
11 |
12 | class _Accent {
13 | const _Accent();
14 |
15 | Gradient get primary => const LinearGradient(
16 | colors: [
17 | Color.fromARGB(255, 255, 87, 221),
18 | Color.fromARGB(255, 86, 194, 255),
19 | ],
20 | );
21 |
22 | Gradient get secondary => const LinearGradient(
23 | colors: [
24 | Color.fromARGB(255, 243, 239, 147),
25 | Color.fromARGB(255, 241, 122, 55),
26 | ],
27 | );
28 | }
29 |
30 | class _Sponsor {
31 | const _Sponsor();
32 |
33 | Gradient get platinum => const LinearGradient(
34 | colors: [
35 | Color.fromARGB(255, 225, 236, 255),
36 | Color.fromARGB(255, 138, 177, 231),
37 | ],
38 | );
39 |
40 | Gradient get gold => const LinearGradient(
41 | colors: [
42 | Color.fromARGB(255, 235, 236, 199),
43 | Color.fromARGB(255, 225, 150, 81),
44 | ],
45 | );
46 |
47 | Gradient get silver => const LinearGradient(
48 | colors: [
49 | Color.fromARGB(255, 211, 211, 212),
50 | Color.fromARGB(255, 158, 176, 178),
51 | ],
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/lib/features/announcement/ui/announcement_section.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/list_bullet.dart';
2 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
3 | import 'package:confwebsite2023/core/theme.dart';
4 | import 'package:confwebsite2023/features/announcement/ui/announcement_section_header.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | /// アナウンスセクション
8 | final class AnnouncementSection extends StatelessWidget {
9 | const AnnouncementSection({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) => const ResponsiveWidget(
13 | largeWidget: _AnnouncementSectionContent(
14 | verticalGap: Spaces.vertical_40,
15 | ),
16 | smallWidget: _AnnouncementSectionContent(
17 | verticalGap: Spaces.vertical_24,
18 | ),
19 | );
20 | }
21 |
22 | /// アナウンスセクションのコンテンツ
23 | final class _AnnouncementSectionContent extends StatelessWidget {
24 | const _AnnouncementSectionContent({required this.verticalGap});
25 |
26 | final SizedBox verticalGap;
27 |
28 | @override
29 | Widget build(BuildContext context) => Column(
30 | children: [
31 | const AnnouncementSectionHeader(),
32 | verticalGap,
33 | const _AnnouncementList(),
34 | ],
35 | );
36 | }
37 |
38 | /// アナウンスのアイテム一覧
39 | final class _AnnouncementList extends StatelessWidget {
40 | const _AnnouncementList();
41 |
42 | static const _verticalGap = Spaces.vertical_16;
43 |
44 | @override
45 | Widget build(BuildContext context) => const Column(
46 | children: [
47 | _AnnouncementItem('会場は禁煙です。喫煙所はありません。'),
48 | _verticalGap,
49 | _AnnouncementItem('クローク/ロッカーなどの用意はございません。手荷物は各自の責任により管理してください。'),
50 | _verticalGap,
51 | _AnnouncementItem('会場内で発生したゴミのお持ち帰りにご協力ください。会場内ではゴミの分別にご協力ください。'),
52 | _verticalGap,
53 | _AnnouncementItem('駐車場の用意はございません。公共交通機関(電車・バスなど)をご利用ください。'),
54 | _verticalGap,
55 | _AnnouncementItem('会場内におけるトラブル、事故やケガ、盗難、紛失等につきましては、会場は一切の責任を負いかねます。'),
56 | _verticalGap,
57 | _AnnouncementItem(
58 | 'イベントの模様は撮影される場合がございます。その場合、お客様が写り込む場合もございますので、予めご了承ください。',
59 | ),
60 | _verticalGap,
61 | _AnnouncementItem(
62 | '''来場者向けの Wi-Fi の提供はございません。登壇者、スポンサー(出し物に関連した利用のみ)にのみ提供いたしますので、予めご了承ください。''',
63 | ),
64 | _verticalGap,
65 | _AnnouncementItem(
66 | '''昼食の提供はございません。会場周辺のお店をご利用ください。また、公式アプリにて会場周辺のお店を紹介しておりますので、ぜひご活用ください。''',
67 | ),
68 | _verticalGap,
69 | _AnnouncementItem(
70 | '''来場者向けの充電スペースは設けません。充電が必要な方はモバイルバッテリーなどをご持参ください。''',
71 | ),
72 | ],
73 | );
74 | }
75 |
76 | /// アナウンスのアイテム
77 | final class _AnnouncementItem extends StatelessWidget {
78 | const _AnnouncementItem(this.text);
79 |
80 | final String text;
81 |
82 | @override
83 | Widget build(BuildContext context) {
84 | final theme = Theme.of(context);
85 |
86 | return Padding(
87 | padding: const EdgeInsets.all(8),
88 | child: Row(
89 | crossAxisAlignment: CrossAxisAlignment.start,
90 | children: [
91 | const Padding(
92 | padding: EdgeInsets.symmetric(vertical: 8),
93 | child: ListBullet(),
94 | ),
95 | Spaces.horizontal_16,
96 | // text
97 | Expanded(
98 | child: Text(
99 | text,
100 | style: theme.textTheme.bodyLarge!.copyWith(
101 | color: baselineColorScheme.ref.secondary.secondary100,
102 | ),
103 | ),
104 | ),
105 | ],
106 | ),
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/features/announcement/ui/announcement_section_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
2 | import 'package:confwebsite2023/core/components/section_header.dart';
3 | import 'package:confwebsite2023/core/theme.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | /// アナウンスセクションのヘッダー
7 | final class AnnouncementSectionHeader extends StatelessWidget {
8 | const AnnouncementSectionHeader({super.key});
9 |
10 | static const _title = 'Announcement';
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | final gradient = GradientConstant.accent.primary;
15 | return ResponsiveWidget(
16 | largeWidget: SectionHeader.leftAlignment(
17 | text: _title,
18 | style: AppTextStyle.pcHeading1,
19 | gradient: gradient,
20 | ),
21 | smallWidget: SectionHeader.leftAlignment(
22 | text: _title,
23 | style: AppTextStyle.spHeading1,
24 | gradient: gradient,
25 | ),
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/features/app_links/ui/app_links.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
4 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | final class AppLinks extends StatelessWidget {
9 | const AppLinks({super.key});
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | final screenSizeType = ResponsiveWidget.getScreenSizeType(context);
14 | final spacing = switch (screenSizeType) {
15 | ScreenSizeType.large || ScreenSizeType.medium => 40.0,
16 | ScreenSizeType.small => 24.0,
17 | };
18 | return Wrap(
19 | alignment: WrapAlignment.center,
20 | spacing: spacing,
21 | runSpacing: spacing,
22 | children: const [
23 | _AppLink.appStore(),
24 | _AppLink.googlePlay(),
25 | ],
26 | );
27 | }
28 | }
29 |
30 | final class _AppLink extends StatelessWidget {
31 | const _AppLink.appStore() : _type = _AppLinkType.appStore;
32 |
33 | const _AppLink.googlePlay() : _type = _AppLinkType.googlePlay;
34 |
35 | final _AppLinkType _type;
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | final onTap = switch (_type) {
40 | _AppLinkType.appStore => () {
41 | final uri = Uri.parse(
42 | 'https://apps.apple.com/app/id6469518498',
43 | );
44 | unawaited(launchUrl(uri));
45 | },
46 | _AppLinkType.googlePlay => () {
47 | final uri = Uri.parse(
48 | 'https://play.google.com/store/apps/details?id=jp.flutterkaigi.conf2023',
49 | );
50 | unawaited(launchUrl(uri));
51 | },
52 | };
53 |
54 | final assetName = switch (_type) {
55 | _AppLinkType.appStore => Assets.appLinks.appStore.keyName,
56 | _AppLinkType.googlePlay => Assets.appLinks.googlePlay.keyName,
57 | };
58 |
59 | final imageButton = Material(
60 | color: Colors.transparent,
61 | child: Ink.image(
62 | image: AssetImage(assetName),
63 | fit: BoxFit.fitHeight,
64 | child: InkWell(
65 | onTap: onTap,
66 | borderRadius: BorderRadius.circular(8),
67 | ),
68 | ),
69 | );
70 |
71 | final screenSizeType = ResponsiveWidget.getScreenSizeType(context);
72 | final height = switch (screenSizeType) {
73 | ScreenSizeType.large || ScreenSizeType.medium => 54.0,
74 | ScreenSizeType.small => 42.0,
75 | };
76 |
77 | final aspectRatio = switch (_type) {
78 | _AppLinkType.appStore => 148 / 54,
79 | _AppLinkType.googlePlay => 182 / 54,
80 | };
81 |
82 | return SizedBox(
83 | height: height,
84 | child: AspectRatio(
85 | aspectRatio: aspectRatio,
86 | child: imageButton,
87 | ),
88 | );
89 | }
90 | }
91 |
92 | enum _AppLinkType {
93 | appStore,
94 | googlePlay,
95 | }
96 |
--------------------------------------------------------------------------------
/lib/features/app_links/ui/app_links_header.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
2 | import 'package:confwebsite2023/core/components/section_header.dart';
3 | import 'package:confwebsite2023/core/theme/app_text_style.dart';
4 | import 'package:confwebsite2023/core/theme/gradient.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | final class AppLinksHeader extends StatelessWidget {
8 | const AppLinksHeader({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | const text = 'Application';
13 | final gradient = GradientConstant.accent.secondary;
14 | return ResponsiveWidget(
15 | largeWidget: SectionHeader.centerAlignment(
16 | text: text,
17 | style: AppTextStyle.pcHeading2,
18 | gradient: gradient,
19 | ),
20 | smallWidget: SectionHeader.centerAlignment(
21 | text: text,
22 | style: AppTextStyle.spHeading2,
23 | gradient: gradient,
24 | ),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/features/app_links/ui/app_links_section.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/theme.dart';
2 | import 'package:confwebsite2023/features/app_links/ui/app_links.dart';
3 | import 'package:confwebsite2023/features/app_links/ui/app_links_header.dart';
4 | import 'package:confwebsite2023/features/app_links/ui/app_links_summary.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | final class AppLinksSection extends StatelessWidget {
8 | const AppLinksSection({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return const Column(
13 | children: [
14 | AppLinksHeader(),
15 | Spaces.vertical_28,
16 | AppLinksSummary(),
17 | Spaces.vertical_36,
18 | AppLinks(),
19 | ],
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/features/app_links/ui/app_links_summary.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | final class AppLinksSummary extends StatelessWidget {
5 | const AppLinksSummary({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | final theme = Theme.of(context);
10 | final textColor = theme.colorScheme.onPrimaryContainer;
11 | final textStyle = theme.textTheme.bodyLarge?.copyWith(
12 | color: textColor,
13 | );
14 | final screenType = ResponsiveWidget.getScreenSizeType(context);
15 | final text = switch (screenType) {
16 | ScreenSizeType.large ||
17 | ScreenSizeType.medium =>
18 | 'FlutterKaigi 2023 のカンファレンスアプリをダウンロード',
19 | ScreenSizeType.small => 'FlutterKaigi 2023 の\nカンファレンスアプリをダウンロード',
20 | };
21 | return Text(
22 | text,
23 | textAlign: TextAlign.center,
24 | style: textStyle,
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/features/conference/model/target_day.dart:
--------------------------------------------------------------------------------
1 | /// Flutter Kaigi が開催される日
2 | final targetDate = DateTime(2023, DateTime.november, 10, 9);
3 |
--------------------------------------------------------------------------------
/lib/features/count_down/model/count_down_timer.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:riverpod_annotation/riverpod_annotation.dart';
4 |
5 | part 'count_down_timer.g.dart';
6 |
7 | @riverpod
8 | DateTime now(NowRef ref) {
9 | final timer = Timer(const Duration(seconds: 1), () {
10 | ref.invalidateSelf();
11 | });
12 | ref.onDispose(timer.cancel);
13 | return DateTime.now();
14 | }
15 |
--------------------------------------------------------------------------------
/lib/features/count_down/model/count_down_timer.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'count_down_timer.dart';
6 |
7 | // **************************************************************************
8 | // RiverpodGenerator
9 | // **************************************************************************
10 |
11 | String _$nowHash() => r'74757bda800201a8415a665d4792f55763ec0f10';
12 |
13 | /// See also [now].
14 | @ProviderFor(now)
15 | final nowProvider = AutoDisposeProvider.internal(
16 | now,
17 | name: r'nowProvider',
18 | debugGetCreateSourceHash:
19 | const bool.fromEnvironment('dart.vm.product') ? null : _$nowHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef NowRef = AutoDisposeProviderRef;
25 | // ignore_for_file: type=lint
26 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member
27 |
--------------------------------------------------------------------------------
/lib/features/count_down/model/count_down_unit.dart:
--------------------------------------------------------------------------------
1 | enum CountDownUnit {
2 | day,
3 | hour,
4 | minute,
5 | second,
6 | }
7 |
--------------------------------------------------------------------------------
/lib/features/count_down/ui/count_down_section.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/foundation/duration_ex.dart';
2 | import 'package:confwebsite2023/core/foundation/iterable_ex.dart';
3 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
4 | import 'package:confwebsite2023/core/theme.dart';
5 | import 'package:confwebsite2023/features/conference/model/target_day.dart';
6 | import 'package:confwebsite2023/features/count_down/model/count_down_timer.dart';
7 | import 'package:confwebsite2023/features/count_down/model/count_down_unit.dart';
8 | import 'package:confwebsite2023/features/count_down/ui/count_down_unit_item.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter_svg/svg.dart';
11 | import 'package:hooks_riverpod/hooks_riverpod.dart';
12 |
13 | class CountDownSection extends ConsumerWidget {
14 | const CountDownSection({
15 | super.key,
16 | });
17 |
18 | static bool isVisible(DateTime now) => !targetDate.difference(now).isNegative;
19 |
20 | @override
21 | Widget build(BuildContext context, WidgetRef ref) {
22 | final remaining = targetDate.difference(ref.watch(nowProvider));
23 |
24 | return Visibility(
25 | visible: !remaining.isNegative,
26 | child: FittedBox(
27 | fit: BoxFit.scaleDown,
28 | child: Center(
29 | child: Row(
30 | mainAxisSize: MainAxisSize.min,
31 | children: [
32 | ...CountDownUnit.values.map(
33 | (unit) {
34 | return CountDownUnitItem(
35 | unit: unit,
36 | value: switch (unit) {
37 | CountDownUnit.day => remaining.clockedDays,
38 | CountDownUnit.hour => remaining.clockedHours,
39 | CountDownUnit.minute => remaining.clockedMinutes,
40 | CountDownUnit.second => remaining.clockedSeconds,
41 | },
42 | );
43 | },
44 | ),
45 | ]
46 | .insertingEach(() => SvgPicture.asset(Assets.icons.dot))
47 | .insertingEach(() => Spaces.horizontal_40),
48 | ),
49 | ),
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/features/count_down/ui/count_down_unit_item.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/foundation/iterable_ex.dart';
2 | import 'package:confwebsite2023/core/theme/dimension.dart';
3 | import 'package:confwebsite2023/features/count_down/model/count_down_unit.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class CountDownUnitItem extends StatelessWidget {
7 | const CountDownUnitItem({
8 | required this.unit,
9 | required this.value,
10 | super.key,
11 | });
12 |
13 | final CountDownUnit unit;
14 | final int value;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | final theme = Theme.of(context);
19 | return Column(
20 | children: [
21 | Text(
22 | value.toString(),
23 | style: const TextStyle(
24 | color: Colors.white,
25 | fontSize: 57,
26 | fontWeight: FontWeight.w900,
27 | height: 1,
28 | ),
29 | ),
30 | Text(
31 | switch (unit) {
32 | CountDownUnit.day => 'DAYS',
33 | CountDownUnit.hour => 'HOURS',
34 | CountDownUnit.minute => 'MINUTES',
35 | CountDownUnit.second => 'SECONDS',
36 | },
37 | style: TextStyle(
38 | color: theme.colorScheme.secondary,
39 | fontSize: 24,
40 | height: 1,
41 | ),
42 | ),
43 | ].insertingEach(() => Spaces.vertical_5),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/features/event/hands-on/data/hands_on_staffs_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/features/staff/data/staff.dart';
2 | import 'package:confwebsite2023/features/staff/data/staff_provider.dart';
3 | import 'package:riverpod_annotation/riverpod_annotation.dart';
4 |
5 | part 'hands_on_staffs_provider.g.dart';
6 |
7 | @riverpod
8 | Future> handsOnStaff(HandsOnStaffRef ref) async {
9 | final staffs = await ref.watch(staffsProvider.future);
10 | const handsOnTeamDisplayNames = ['okaryo', 'ちっぴー', '兼高理恵 (robo)', 'jiyuujin'];
11 | return staffs
12 | .where((staff) => handsOnTeamDisplayNames.contains(staff.displayName))
13 | .toList();
14 | }
15 |
--------------------------------------------------------------------------------
/lib/features/event/hands-on/data/hands_on_staffs_provider.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'hands_on_staffs_provider.dart';
6 |
7 | // **************************************************************************
8 | // RiverpodGenerator
9 | // **************************************************************************
10 |
11 | String _$handsOnStaffHash() => r'6c45dd0845d6b7d0ecfdc1e859c323a8c06fd3f7';
12 |
13 | /// See also [handsOnStaff].
14 | @ProviderFor(handsOnStaff)
15 | final handsOnStaffProvider = AutoDisposeFutureProvider>.internal(
16 | handsOnStaff,
17 | name: r'handsOnStaffProvider',
18 | debugGetCreateSourceHash:
19 | const bool.fromEnvironment('dart.vm.product') ? null : _$handsOnStaffHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef HandsOnStaffRef = AutoDisposeFutureProviderRef>;
25 | // ignore_for_file: type=lint
26 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member
27 |
--------------------------------------------------------------------------------
/lib/features/event/hands-on/ui/hands_on_event.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/profile_image.dart';
2 | import 'package:confwebsite2023/core/components/wanted.dart';
3 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
4 | import 'package:confwebsite2023/features/event/hands-on/data/hands_on_staffs_provider.dart';
5 | import 'package:confwebsite2023/features/staff/data/staff.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:hooks_riverpod/hooks_riverpod.dart';
8 | import 'package:url_launcher/url_launcher_string.dart';
9 |
10 | class HandsOnEvent extends StatelessWidget {
11 | const HandsOnEvent({super.key});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | const text = '今年のハンズオンは10月26日をもって終了しました。多くの皆様にご参加いただき誠にありがとうございました。';
16 | return WantedWidget(
17 | title: 'Thanks for joining us!',
18 | ticketButtonTitle: 'イベント詳細ページ',
19 | content: text,
20 | ticketOnPressed: () async => launchUrlString(
21 | 'https://flutterkaigi.connpass.com/event/293847/',
22 | mode: LaunchMode.externalApplication,
23 | ),
24 | archiveButtonTitle: 'アーカイブはこちら',
25 | archiveOnPressed: () async => launchUrlString(
26 | 'https://www.youtube.com/watch?v=Kj-3UcIZc4Y',
27 | mode: LaunchMode.externalApplication,
28 | ),
29 | image: Assets.illustrationConference,
30 | child: const _HandsOnStaffsIcon(),
31 | );
32 | }
33 | }
34 |
35 | class _HandsOnStaffsIcon extends ConsumerWidget {
36 | const _HandsOnStaffsIcon();
37 |
38 | @override
39 | Widget build(BuildContext context, WidgetRef ref) {
40 | final staffs = ref.watch(handsOnStaffProvider);
41 |
42 | return switch ((staffs.valueOrNull, staffs.error)) {
43 | (final List data, _) => Wrap(
44 | alignment: WrapAlignment.center,
45 | runAlignment: WrapAlignment.center,
46 | crossAxisAlignment: WrapCrossAlignment.center,
47 | spacing: 24,
48 | children: data
49 | .map(
50 | (e) => ProfileImage(
51 | imageUrl: e.image.src,
52 | size: 120,
53 | ),
54 | )
55 | .toList(),
56 | ),
57 | (_, final Object error) => Text('例外が発生しました: $error'),
58 | (_, _) => const CircularProgressIndicator(),
59 | };
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/features/footer/ui/footer.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/features/footer/ui/footer_copyright.dart';
2 | import 'package:confwebsite2023/features/footer/ui/footer_links.dart';
3 | import 'package:confwebsite2023/features/footer/ui/footer_other_year_links.dart';
4 | import 'package:confwebsite2023/features/footer/ui/footer_sns_links.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | class Footer extends StatelessWidget {
8 | const Footer({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return SizedBox(
13 | width: MediaQuery.sizeOf(context).width,
14 | child: const DecoratedBox(
15 | decoration: BoxDecoration(
16 | gradient: LinearGradient(
17 | begin: Alignment.topCenter,
18 | end: Alignment.bottomCenter,
19 | colors: [
20 | Color(0xFF511486),
21 | Color(0xFF391940),
22 | ],
23 | ),
24 | ),
25 | child: Column(
26 | children: [
27 | SizedBox(height: 20),
28 | FooterOtherYearLinks(),
29 | SizedBox(height: 20),
30 | FooterSnsLinks(),
31 | SizedBox(height: 20),
32 | FooterLinks(),
33 | SizedBox(height: 20),
34 | FooterCopyright(),
35 | SizedBox(height: 80),
36 | ],
37 | ),
38 | ),
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lib/features/footer/ui/footer_copyright.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/theme.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class FooterCopyright extends StatelessWidget {
5 | const FooterCopyright({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | final copyrightTextStyle = Theme.of(context).textTheme.bodyLarge?.copyWith(
10 | color: baselineColorScheme.ref.secondary.secondary80,
11 | );
12 | return Column(
13 | children: [
14 | Text(
15 | '© 2021-2023 FlutterKaigi 実行委員会',
16 | style: copyrightTextStyle,
17 | ),
18 | Spaces.vertical_20,
19 | Text(
20 | 'Flutter and the related logo are trademarks of Google LLC. '
21 | 'FlutterKaigi is not affiliated with or otherwise sponsored '
22 | 'by Google LLC.',
23 | textAlign: TextAlign.center,
24 | style: copyrightTextStyle,
25 | ),
26 | Spaces.vertical_20,
27 | Wrap(
28 | crossAxisAlignment: WrapCrossAlignment.center,
29 | alignment: WrapAlignment.center,
30 | runSpacing: 8,
31 | spacing: 8,
32 | children: [
33 | Text(
34 | 'The "Flutter" name and the Flutter logo',
35 | style: copyrightTextStyle,
36 | ),
37 | const ColorFiltered(
38 | colorFilter: ColorFilter.mode(
39 | Colors.white,
40 | BlendMode.srcATop,
41 | ),
42 | child: FlutterLogo(size: 24),
43 | ),
44 | Text(
45 | 'are trademarks owned by Google.',
46 | style: copyrightTextStyle,
47 | ),
48 | ],
49 | ),
50 | ],
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/features/footer/ui/footer_links.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:url_launcher/link.dart';
3 |
4 | class FooterLinks extends StatelessWidget {
5 | const FooterLinks({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return const Wrap(
10 | crossAxisAlignment: WrapCrossAlignment.center,
11 | alignment: WrapAlignment.spaceAround,
12 | spacing: 24,
13 | children: [
14 | Row(
15 | mainAxisSize: MainAxisSize.min,
16 | children: [
17 | _Link.codeOfConduct(),
18 | SizedBox(width: 24),
19 | _Link.privacyPolicy(),
20 | ],
21 | ),
22 | Row(
23 | mainAxisSize: MainAxisSize.min,
24 | children: [
25 | _Link.contact(),
26 | SizedBox(width: 24),
27 | _LicenseLink(),
28 | ],
29 | ),
30 | ],
31 | );
32 | }
33 | }
34 |
35 | class _Link extends StatelessWidget {
36 | const _Link.codeOfConduct()
37 | : semanticsLabel = 'Code of Conduct',
38 | url =
39 | 'https://flutterkaigi.github.io/flutterkaigi/Code-of-Conduct.ja.html',
40 | text = 'Code of Conduct';
41 |
42 | const _Link.privacyPolicy()
43 | : semanticsLabel = 'Privacy Policy',
44 | url =
45 | 'https://flutterkaigi.github.io/flutterkaigi/Privacy-Policy.ja.html',
46 | text = 'Privacy Policy';
47 |
48 | const _Link.contact()
49 | : semanticsLabel = 'Contact',
50 | url =
51 | 'https://docs.google.com/forms/d/e/1FAIpQLSemYPFEWpP8594MWI4k3Nz45RJzMS7pz1ufwtnX4t3V7z2TOw/viewform',
52 | text = 'Contact';
53 |
54 | final String semanticsLabel;
55 | final String url;
56 | final String text;
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | final linkTextStyle = Theme.of(context).textTheme.bodyLarge;
61 | return Semantics(
62 | label: semanticsLabel,
63 | enabled: true,
64 | readOnly: true,
65 | child: Link(
66 | uri: Uri.tryParse(url),
67 | target: LinkTarget.blank,
68 | builder: (context, openLink) {
69 | return TextButton(
70 | onPressed: openLink,
71 | child: Text(
72 | text,
73 | style: linkTextStyle,
74 | ),
75 | );
76 | },
77 | ),
78 | );
79 | }
80 | }
81 |
82 | class _LicenseLink extends StatelessWidget {
83 | const _LicenseLink();
84 |
85 | @override
86 | Widget build(BuildContext context) {
87 | final linkTextStyle = Theme.of(context).textTheme.bodyLarge;
88 | return Semantics(
89 | label: 'License',
90 | enabled: true,
91 | readOnly: true,
92 | child: TextButton(
93 | onPressed: () async {
94 | await Navigator.push(
95 | context,
96 | MaterialPageRoute(
97 | builder: (context) => const LicensePage(),
98 | ),
99 | );
100 | },
101 | child: Text(
102 | 'License',
103 | style: linkTextStyle,
104 | ),
105 | ),
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/features/footer/ui/footer_other_year_links.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:url_launcher/link.dart';
3 |
4 | class FooterOtherYearLinks extends StatelessWidget {
5 | const FooterOtherYearLinks({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return const Wrap(
10 | crossAxisAlignment: WrapCrossAlignment.center,
11 | children: [
12 | _OtherYearsLink.$2021(),
13 | _OtherYearsLinkSlash(),
14 | _OtherYearsLink.$2022(),
15 | ],
16 | );
17 | }
18 | }
19 |
20 | class _OtherYearsLink extends StatelessWidget {
21 | const _OtherYearsLink.$2021()
22 | : semanticsLabel = '2021年のサイト',
23 | url = 'https://flutterkaigi.jp/2021/',
24 | text = '2021';
25 |
26 | const _OtherYearsLink.$2022()
27 | : semanticsLabel = '2022年のサイト',
28 | url = 'https://flutterkaigi.jp/2022/',
29 | text = '2022';
30 |
31 | final String semanticsLabel;
32 | final String url;
33 | final String text;
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | final otherYearsLinkTextStyle = Theme.of(context).textTheme.bodyLarge;
38 | return Semantics(
39 | label: semanticsLabel,
40 | enabled: true,
41 | readOnly: true,
42 | child: Link(
43 | uri: Uri.tryParse(url),
44 | target: LinkTarget.blank,
45 | builder: (context, openLink) {
46 | return TextButton(
47 | onPressed: openLink,
48 | child: Text(
49 | text,
50 | style: otherYearsLinkTextStyle,
51 | ),
52 | );
53 | },
54 | ),
55 | );
56 | }
57 | }
58 |
59 | class _OtherYearsLinkSlash extends StatelessWidget {
60 | const _OtherYearsLinkSlash();
61 |
62 | @override
63 | Widget build(BuildContext context) {
64 | final otherYearsLinkSlashTextStyle = Theme.of(context).textTheme.labelLarge;
65 | return Padding(
66 | padding: const EdgeInsets.symmetric(horizontal: 20),
67 | child: Text(
68 | '/',
69 | style: otherYearsLinkSlashTextStyle,
70 | ),
71 | );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/features/footer/ui/footer_sns_links.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
2 | import 'package:confwebsite2023/core/theme.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_svg/svg.dart';
5 | import 'package:url_launcher/link.dart';
6 |
7 | class FooterSnsLinks extends StatelessWidget {
8 | const FooterSnsLinks({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Wrap(
13 | crossAxisAlignment: WrapCrossAlignment.center,
14 | children: [
15 | _SnsLink(
16 | semanticsLabel: 'FlutterKaigiのTwitter',
17 | linkUrl: 'https://twitter.com/FlutterKaigi',
18 | icon: Assets.icons.twitter,
19 | ),
20 | Spaces.horizontal_40,
21 | _SnsLink(
22 | semanticsLabel: 'FlutterKaigiのGitHub',
23 | linkUrl: 'https://github.com/FlutterKaigi',
24 | icon: Assets.icons.github,
25 | ),
26 | Spaces.horizontal_40,
27 | _SnsLink(
28 | semanticsLabel: 'FlutterKaigiのDiscord',
29 | linkUrl: 'https://discord.com/invite/Nr7H8JTJSF',
30 | icon: Assets.icons.discord,
31 | ),
32 | Spaces.horizontal_40,
33 | _SnsLink(
34 | semanticsLabel: 'FlutterKaigiのMedium',
35 | linkUrl: 'https://medium.com/flutterkaigi',
36 | icon: Assets.icons.medium,
37 | ),
38 | ],
39 | );
40 | }
41 | }
42 |
43 | class _SnsLink extends StatelessWidget {
44 | const _SnsLink({
45 | required this.semanticsLabel,
46 | required this.linkUrl,
47 | required this.icon,
48 | });
49 |
50 | final String semanticsLabel;
51 | final String linkUrl;
52 | final String icon;
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | return Semantics(
57 | label: semanticsLabel,
58 | enabled: true,
59 | readOnly: true,
60 | child: Link(
61 | uri: Uri.tryParse(linkUrl),
62 | target: LinkTarget.blank,
63 | builder: (context, openLink) {
64 | return IconButton(
65 | onPressed: openLink,
66 | icon: SvgPicture.asset(
67 | icon,
68 | width: 40,
69 | height: 40,
70 | ),
71 | color: baselineColorScheme.white,
72 | );
73 | },
74 | ),
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/features/header/data/header_item_button_data.dart:
--------------------------------------------------------------------------------
1 | class HeaderItemButtonData {
2 | const HeaderItemButtonData({
3 | required this.title,
4 | required this.onPressed,
5 | });
6 |
7 | final String title;
8 | final void Function() onPressed;
9 | }
10 |
--------------------------------------------------------------------------------
/lib/features/header/ui/flutter_kaigi_sns_links.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/gen/assets.gen.dart';
2 | import 'package:confwebsite2023/core/theme.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_svg/svg.dart';
5 | import 'package:url_launcher/link.dart';
6 |
7 | class FlutterKaigiSnsLinks extends StatelessWidget {
8 | const FlutterKaigiSnsLinks({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Wrap(
13 | alignment: WrapAlignment.center,
14 | spacing: 40,
15 | children: FlutterKaigiSnsLink.values
16 | .map(
17 | (e) => SnsIconWidget(
18 | assetName: e.imageAssetName,
19 | semanticsLabel: e.semanticsLabel,
20 | url: e.url,
21 | ),
22 | )
23 | .toList(),
24 | );
25 | }
26 | }
27 |
28 | class SnsIconWidget extends StatelessWidget {
29 | const SnsIconWidget({
30 | required this.assetName,
31 | required this.url,
32 | required this.semanticsLabel,
33 | super.key,
34 | });
35 |
36 | final String assetName;
37 | final String url;
38 | final String? semanticsLabel;
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 | return Semantics(
43 | label: semanticsLabel,
44 | enabled: true,
45 | child: Link(
46 | uri: Uri.tryParse(url),
47 | target: LinkTarget.blank,
48 | builder: (context, openLink) {
49 | return IconButton(
50 | onPressed: openLink,
51 | icon: SvgPicture.asset(
52 | assetName,
53 | width: 40,
54 | height: 40,
55 | ),
56 | color: baselineColorScheme.white,
57 | );
58 | },
59 | ),
60 | );
61 | }
62 | }
63 |
64 | enum FlutterKaigiSnsLink {
65 | twitter(
66 | 'https://twitter.com/FlutterKaigi',
67 | 'FlutterKaigiのTwitter',
68 | ),
69 | github(
70 | 'https://github.com/FlutterKaigi',
71 | 'FlutterKaigiのGitHub',
72 | ),
73 | discord(
74 | 'https://discord.com/invite/Nr7H8JTJSF',
75 | 'FlutterKaigiのDiscord',
76 | ),
77 | medium(
78 | 'https://medium.com/flutterkaigi',
79 | 'FlutterKaigiのMedium',
80 | );
81 |
82 | const FlutterKaigiSnsLink(
83 | this.url,
84 | this.semanticsLabel,
85 | );
86 | final String url;
87 | final String semanticsLabel;
88 |
89 | String get imageAssetName => switch (this) {
90 | FlutterKaigiSnsLink.twitter => Assets.icons.twitter,
91 | FlutterKaigiSnsLink.github => Assets.icons.github,
92 | FlutterKaigiSnsLink.discord => Assets.icons.discord,
93 | FlutterKaigiSnsLink.medium => Assets.icons.medium,
94 | };
95 | }
96 |
--------------------------------------------------------------------------------
/lib/features/hero/ui/hero_section.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/responsive_widget.dart';
2 | import 'package:confwebsite2023/features/hero/ui/hero_section_desktop.dart';
3 | import 'package:confwebsite2023/features/hero/ui/hero_section_mobile.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | class HeroSection extends StatelessWidget {
7 | const HeroSection({super.key});
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return const ResponsiveWidget(
12 | largeWidget: HeroSectionDesktop(),
13 | smallWidget: HeroSectionMobile(),
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lib/features/hero/ui/hero_section_twitter.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/theme.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_svg/flutter_svg.dart';
4 | import 'package:url_launcher/link.dart';
5 |
6 | class HeroSectionTwitter extends StatelessWidget {
7 | const HeroSectionTwitter({
8 | required this.backgroundColor,
9 | required this.icon,
10 | required this.iconColor,
11 | required this.title,
12 | required this.subTitle,
13 | required this.url,
14 | required this.isMobile,
15 | this.titleTextStyle,
16 | this.subTitleTextStyle,
17 | super.key,
18 | });
19 |
20 | final Color backgroundColor;
21 | final String icon;
22 | final Color iconColor;
23 | final String title;
24 | final String subTitle;
25 | final String url;
26 | final bool isMobile;
27 | final TextStyle? titleTextStyle;
28 | final TextStyle? subTitleTextStyle;
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Padding(
33 | padding: const EdgeInsets.symmetric(horizontal: 16),
34 | child: Link(
35 | uri: Uri.parse(url),
36 | target: LinkTarget.blank,
37 | builder: (context, openLink) {
38 | return MouseRegion(
39 | cursor: SystemMouseCursors.click,
40 | hitTestBehavior: HitTestBehavior.deferToChild,
41 | child: GestureDetector(
42 | onTap: openLink,
43 | behavior: HitTestBehavior.deferToChild,
44 | child: FittedBox(
45 | fit: BoxFit.scaleDown,
46 | child: Container(
47 | width: isMobile ? 358 : 744,
48 | padding: EdgeInsets.symmetric(
49 | horizontal: 30,
50 | vertical: isMobile ? 8 : 12,
51 | ),
52 | decoration: BoxDecoration(
53 | color: backgroundColor,
54 | borderRadius: BorderRadius.circular(100),
55 | border: Border.all(
56 | color: baselineColorScheme.ref.primary.primary90,
57 | width: 2,
58 | ),
59 | boxShadow: [
60 | BoxShadow(
61 | color: baselineColorScheme.white.withOpacity(0.35),
62 | offset: const Offset(0, 2),
63 | blurRadius: 16,
64 | ),
65 | ],
66 | ),
67 | child: Row(
68 | mainAxisSize: MainAxisSize.min,
69 | children: [
70 | SizedBox(
71 | width: 40,
72 | height: 40,
73 | child: SvgPicture.asset(
74 | icon,
75 | colorFilter: ColorFilter.mode(
76 | iconColor,
77 | BlendMode.srcIn,
78 | ),
79 | ),
80 | ),
81 | Spaces.horizontal_20,
82 | Text(
83 | title,
84 | style: titleTextStyle,
85 | ),
86 | if (isMobile) const SizedBox.shrink() else const Spacer(),
87 | Text(
88 | subTitle,
89 | style: subTitleTextStyle,
90 | ),
91 | ],
92 | ),
93 | ),
94 | ),
95 | ),
96 | );
97 | },
98 | ),
99 | );
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/lib/features/news/data/news.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'news.freezed.dart';
4 | part 'news.g.dart';
5 |
6 | @freezed
7 | class News with _$News {
8 | const factory News({
9 | required String text,
10 | required String? url,
11 | required DateTime startedAt,
12 | @JsonKey(fromJson: _dateTimeFromJson, toJson: _dateTimeToJson)
13 | required DateTime? endedAt,
14 | }) = _News;
15 |
16 | factory News.fromJson(Map json) => _$NewsFromJson(json);
17 | }
18 |
19 | DateTime? _dateTimeFromJson(String? json) => DateTime.tryParse(json.toString());
20 |
21 | String? _dateTimeToJson(DateTime? instance) => instance?.toIso8601String();
22 |
--------------------------------------------------------------------------------
/lib/features/news/data/news.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'news.dart';
6 |
7 | // **************************************************************************
8 | // JsonSerializableGenerator
9 | // **************************************************************************
10 |
11 | _$_News _$$_NewsFromJson(Map json) => $checkedCreate(
12 | r'_$_News',
13 | json,
14 | ($checkedConvert) {
15 | final val = _$_News(
16 | text: $checkedConvert('text', (v) => v as String),
17 | url: $checkedConvert('url', (v) => v as String?),
18 | startedAt:
19 | $checkedConvert('startedAt', (v) => DateTime.parse(v as String)),
20 | endedAt: $checkedConvert(
21 | 'endedAt', (v) => _dateTimeFromJson(v as String?)),
22 | );
23 | return val;
24 | },
25 | );
26 |
27 | Map _$$_NewsToJson(_$_News instance) => {
28 | 'text': instance.text,
29 | 'url': instance.url,
30 | 'startedAt': instance.startedAt.toIso8601String(),
31 | 'endedAt': _dateTimeToJson(instance.endedAt),
32 | };
33 |
--------------------------------------------------------------------------------
/lib/features/news/data/news_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:confwebsite2023/features/news/data/news.dart';
4 | import 'package:flutter_dotenv/flutter_dotenv.dart';
5 | import 'package:http/http.dart' as http;
6 | import 'package:riverpod_annotation/riverpod_annotation.dart';
7 |
8 | part 'news_data_source.g.dart';
9 |
10 | @Riverpod(keepAlive: true)
11 | NewsDataSource newsDataSource(NewsDataSourceRef ref) => NewsDataSource();
12 |
13 | class NewsDataSource {
14 | static const spaceUid = 'flutterkaigi';
15 | static const appUid = 'flutterkaigi-2023';
16 |
17 | Future> fetchNewsItems() async {
18 | final result = await http.get(
19 | Uri.parse('https://$spaceUid.cdn.newt.so/v1/$appUid/news'),
20 | headers: {
21 | 'Authorization': 'Bearer ${dotenv.env['NEWT_CDN_API_TOKEN']!}',
22 | },
23 | );
24 | final jsonResult = json.decode(result.body) as Map;
25 | final itemsJson = jsonResult['items'] as List;
26 | return itemsJson
27 | .map(
28 | (e) => News.fromJson(e as Map),
29 | )
30 | .toList();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/features/news/data/news_data_source.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'news_data_source.dart';
6 |
7 | // **************************************************************************
8 | // RiverpodGenerator
9 | // **************************************************************************
10 |
11 | String _$newsDataSourceHash() => r'63806afffd21852516bb98e0023c5d437950f5ab';
12 |
13 | /// See also [newsDataSource].
14 | @ProviderFor(newsDataSource)
15 | final newsDataSourceProvider = Provider.internal(
16 | newsDataSource,
17 | name: r'newsDataSourceProvider',
18 | debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
19 | ? null
20 | : _$newsDataSourceHash,
21 | dependencies: null,
22 | allTransitiveDependencies: null,
23 | );
24 |
25 | typedef NewsDataSourceRef = ProviderRef;
26 | // ignore_for_file: type=lint
27 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member
28 |
--------------------------------------------------------------------------------
/lib/features/news/data/news_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/features/news/data/news.dart';
2 | import 'package:confwebsite2023/features/news/data/news_data_source.dart';
3 | import 'package:riverpod_annotation/riverpod_annotation.dart';
4 |
5 | part 'news_provider.g.dart';
6 |
7 | @riverpod
8 | Future> news(NewsRef ref) =>
9 | ref.watch(newsDataSourceProvider).fetchNewsItems();
10 |
11 | /// `startedAt`が現在時刻よりも未来 かつ `endedAt`が現在時刻よりも過去のNewsのみを返す
12 | @riverpod
13 | Future> currentNews(CurrentNewsRef ref) async {
14 | final news = await ref.watch(newsProvider.future);
15 | final now = DateTime.now();
16 | return news
17 | .where(
18 | (e) =>
19 | e.startedAt.toLocal().isBefore(now) &&
20 | (e.endedAt?.toLocal().isAfter(now) ?? true),
21 | )
22 | .toList()
23 | ..sort(
24 | (a, b) => b.startedAt.compareTo(a.startedAt),
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/lib/features/news/data/news_provider.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | // ignore_for_file: type=lint, duplicate_ignore
4 |
5 | part of 'news_provider.dart';
6 |
7 | // **************************************************************************
8 | // RiverpodGenerator
9 | // **************************************************************************
10 |
11 | String _$newsHash() => r'000c2964b30434e9cb81330c2d1a7ecebd0c9be6';
12 |
13 | /// See also [news].
14 | @ProviderFor(news)
15 | final newsProvider = AutoDisposeFutureProvider>.internal(
16 | news,
17 | name: r'newsProvider',
18 | debugGetCreateSourceHash:
19 | const bool.fromEnvironment('dart.vm.product') ? null : _$newsHash,
20 | dependencies: null,
21 | allTransitiveDependencies: null,
22 | );
23 |
24 | typedef NewsRef = AutoDisposeFutureProviderRef>;
25 | String _$currentNewsHash() => r'362028bd457b4952250ee590d11bfa31b9f82b9c';
26 |
27 | /// `startedAt`が現在時刻よりも未来 かつ `endedAt`が現在時刻よりも過去のNewsのみを返す
28 | ///
29 | /// Copied from [currentNews].
30 | @ProviderFor(currentNews)
31 | final currentNewsProvider = AutoDisposeFutureProvider>.internal(
32 | currentNews,
33 | name: r'currentNewsProvider',
34 | debugGetCreateSourceHash:
35 | const bool.fromEnvironment('dart.vm.product') ? null : _$currentNewsHash,
36 | dependencies: null,
37 | allTransitiveDependencies: null,
38 | );
39 |
40 | typedef CurrentNewsRef = AutoDisposeFutureProviderRef>;
41 | // ignore_for_file: type=lint
42 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member
43 |
--------------------------------------------------------------------------------
/lib/features/news/ui/news_section.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/core/components/list_bullet.dart';
2 | import 'package:confwebsite2023/core/theme.dart';
3 | import 'package:confwebsite2023/features/news/data/news.dart';
4 | import 'package:confwebsite2023/features/news/data/news_provider.dart';
5 | import 'package:flutter/material.dart';
6 | import 'package:hooks_riverpod/hooks_riverpod.dart';
7 | import 'package:intl/intl.dart';
8 | import 'package:url_launcher/link.dart';
9 |
10 | class NewsSection extends ConsumerWidget {
11 | const NewsSection({super.key});
12 |
13 | @override
14 | Widget build(BuildContext context, WidgetRef ref) {
15 | final state = ref.watch(currentNewsProvider);
16 | return state.when(
17 | data: (data) => Column(
18 | children: [
19 | for (final e in data) _NewsItem(news: e),
20 | if (data.isNotEmpty) Spaces.vertical_82,
21 | ],
22 | ),
23 | error: (error, _) => Text('エラーが発生しました: $error'),
24 | loading: CircularProgressIndicator.adaptive,
25 | );
26 | }
27 | }
28 |
29 | class _NewsItem extends StatelessWidget {
30 | const _NewsItem({required this.news});
31 |
32 | final News news;
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | final theme = Theme.of(context);
37 | final child = Row(
38 | children: [
39 | // 日付
40 | Text(
41 | DateFormat('yyyy/MM/dd').format(news.startedAt),
42 | style: theme.textTheme.bodyLarge!.copyWith(
43 | color: baselineColorScheme.ref.primary.primary90,
44 | ),
45 | ),
46 | // 紫丸ポチ
47 | Spaces.horizontal_16,
48 | const ListBullet(),
49 | Spaces.horizontal_16,
50 | // text
51 | Expanded(
52 | child: Text(
53 | news.text,
54 | style: theme.textTheme.bodyLarge!.copyWith(
55 | color: baselineColorScheme.ref.secondary.secondary100,
56 | ),
57 | ),
58 | ),
59 | ],
60 | );
61 |
62 | // urlがある場合
63 | if (news.url != null) {
64 | return Link(
65 | uri: Uri.tryParse(news.url!),
66 | target: LinkTarget.blank,
67 | builder: (_, followLink) => TextButton(
68 | style: TextButton.styleFrom(
69 | padding: const EdgeInsets.all(8),
70 | alignment: Alignment.centerLeft,
71 | ),
72 | onPressed: followLink,
73 | child: child,
74 | ),
75 | );
76 | } else {
77 | return Padding(
78 | padding: const EdgeInsets.all(8),
79 | child: child,
80 | );
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/lib/features/session/data/session.dart:
--------------------------------------------------------------------------------
1 | import 'package:confwebsite2023/features/session/data/speaker.dart';
2 | import 'package:confwebsite2023/features/session/data/tag.dart';
3 | import 'package:confwebsite2023/features/session/data/track.dart';
4 | import 'package:freezed_annotation/freezed_annotation.dart';
5 | import 'package:intl/intl.dart';
6 |
7 | part 'session.freezed.dart';
8 |
9 | part 'session.g.dart';
10 |
11 | @Freezed(unionKey: 'type')
12 | sealed class Session with _$Session {
13 | @JsonSerializable(fieldRename: FieldRename.snake)
14 | const factory Session.timeslot({
15 | required String uuid,
16 | required String title,
17 | required DateTime startsAt,
18 | required int lengthMin,
19 | required Track track,
20 | String? abstract,
21 | Speaker? speaker,
22 | }) = TimeslotSession;
23 |
24 | @JsonSerializable(fieldRename: FieldRename.snake)
25 | const factory Session.talk({
26 | required String uuid,
27 | required String url,
28 | required String title,
29 | required String abstract,
30 | required Track track,
31 | required DateTime startsAt,
32 | required int lengthMin,
33 | required List tags,
34 | required Speaker speaker,
35 | }) = TalkSession;
36 |
37 | const factory Session.lunch({
38 | required DateTime startsAt,
39 | required int lengthMin,
40 | }) = LunchSession;
41 |
42 | factory Session.fromJson(Map json) =>
43 | _$SessionFromJson(json);
44 | }
45 |
46 | extension TalkSessionIsSponsored on TalkSession {
47 | bool get isSponsored {
48 | return tags.any((tag) => tag.name == 'Sponsored');
49 | }
50 | }
51 |
52 | final _formatterLong = DateFormat('yyyy年MM月dd日 HH:mm');
53 | final _formatterShort = DateFormat('HH:mm');
54 |
55 | extension SessionTimeText on Session {
56 | DateTime get _endsAt {
57 | return startsAt.add(Duration(minutes: lengthMin));
58 | }
59 |
60 | String get timeRangeLongText {
61 | final startsAtText = _formatterLong.format(startsAt.toLocal());
62 | final endsAtText = _formatterShort.format(_endsAt.toLocal());
63 | final lengthMinText = '$lengthMin分';
64 | return '$startsAtText〜$endsAtText($lengthMinText)';
65 | }
66 |
67 | String get timeRangeShortText {
68 | final startsAtText = _formatterShort.format(startsAt.toLocal());
69 | final endsAtText = _formatterShort.format(_endsAt.toLocal());
70 | return '$startsAtText〜$endsAtText';
71 | }
72 | }
73 |
74 | extension SessionYoutubeUrlEx on TalkSession {
75 | String get youtubeUrl => switch (uuid) {
76 | // Add Material touch ripples
77 | '21abbd32-6864-487a-8066-c9d7f7e5e9be' => 'cMS2FzbhXJ8',
78 | // 出前館におけるFlutterの現在とこれから
79 | '5b402df3-9e5d-4c0b-80fa-61d9ba356594' => 'bKnvn-Orpd0',
80 | // DartによるBFF構築・運用 〜Dart Frog×melos〜
81 | '972ffbac-422b-4d4b-9686-f59c4438da04' => 'jNmyr-TZVfU',
82 | // ゆめみの Flutter エンジニア育成方法
83 | '84b1350e-b76e-4cdc-884b-37a4a6e14846' => 'UGxzjYNHH90',
84 | // Flutterアプリのセキュリティ対策を考えてみる
85 | '090ad5b8-7066-40e6-8ca8-58fff766f046' => 'lObSpRxP1Do',
86 | // 我々にはなぜ Riverpod が必要なのか - InheritedWidget から始まる app state 管理手法の課題
87 | '0b32515e-2c80-45f4-8ea2-c6c269d2609f' => '2SnTNFAzmY0',
88 | // Flutter アプリにおけるテスト戦略の見直しと自動テストの導入
89 | 'df52c995-5fbb-4ff0-abbc-e6332af98797' => 'VHhZlTDfwIQ',
90 | // 詳解!Flutterにおける課金実装
91 | '67df96a0-4f03-401a-b740-c296a5bcbd86' => 'NcfrY-EN8Pg',
92 | // Master of Flutter lifecycle
93 | 'f76c37b8-172d-4072-ad4a-bd870bc15728' => 'sznsAolD6dI',
94 | // Dartのコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について
95 | 'd9cc75af-a3a2-4d0e-af6c-f12aa143ba4c' => 'EKoI-p1UnNk',
96 | // Flutterで構築する漫画ビューア
97 | '076d093c-a1ff-4fe3-be58-8f8536c97de3' => 'vCoG6BTNpAA',
98 | // 魅せろ! Flutter で目を惹く UI デザインを実装する
99 | '2b0118b0-52d2-4a9c-a564-faf50651dea2' => '6zLih07J3RU',
100 | _ => '',
101 | };
102 | }
103 |
--------------------------------------------------------------------------------
/lib/features/session/data/session_data_source.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 |
3 | import 'package:confwebsite2023/features/session/data/session.dart';
4 | import 'package:http/http.dart' as http;
5 | import 'package:json_annotation/json_annotation.dart';
6 |
7 | class SessionDataSource {
8 | static const conferenceId = 'flutterkaigi-2023';
9 |
10 | Future> fetchSessions() async {
11 | final result = await http.get(
12 | Uri.parse('https://fortee.jp/$conferenceId/api/timetable'),
13 | );
14 | // Prevent garbled characters
15 | final body = utf8.decode(result.bodyBytes);
16 | final jsonResult = json.decode(body) as Map;
17 | final itemsJson = jsonResult['timetable'] as List;
18 | return itemsJson
19 | .map(
20 | (e) {
21 | try {
22 | return Session.fromJson(e as Map);
23 | } on CheckedFromJsonException catch (e) {
24 | if (e.key == 'track') {
25 | // Exclude sessions whose track is not decided, such as
26 | // substitute sessions.
27 | return null;
28 | }
29 | rethrow;
30 | }
31 | },
32 | )
33 | .nonNulls
34 | .toList();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/features/session/data/session_provider.dart:
--------------------------------------------------------------------------------
1 | import 'package:collection/collection.dart';
2 | import 'package:confwebsite2023/features/session/data/session.dart';
3 | import 'package:confwebsite2023/features/session/data/track.dart';
4 | import 'package:confwebsite2023/features/sponsor/data/sponsor.dart';
5 | import 'package:confwebsite2023/features/sponsor/data/sponsor_data_source.dart';
6 | import 'package:confwebsite2023/features/sponsor/data/sponsor_plan.dart';
7 | import 'package:riverpod_annotation/riverpod_annotation.dart';
8 |
9 | part 'session_provider.g.dart';
10 |
11 | @Riverpod(keepAlive: true)
12 | List sessions(SessionsRef ref) => throw UnimplementedError();
13 |
14 | @Riverpod(
15 | dependencies: [
16 | sessions,
17 | ],
18 | )
19 | Iterable> sessionsGroupListsByStartsAt(
20 | SessionsGroupListsByStartsAtRef ref,
21 | ) {
22 | final sessions = ref.watch(sessionsProvider);
23 | return sessions.groupListsBy((s) => s.startsAt).values;
24 | }
25 |
26 | typedef Tracks = ({Track left, Track right});
27 |
28 | @Riverpod(
29 | dependencies: [
30 | sessions,
31 | ],
32 | )
33 | Tracks tracks(TracksRef ref) {
34 | final sessions = ref.watch(sessionsProvider);
35 | final tracks = sessions
36 | .map((s) {
37 | if (s is! TalkSession) {
38 | return null;
39 | }
40 | return s.track;
41 | })
42 | .whereType