diff --git a/.github/workflows/_build-android-app.yml b/.github/workflows/_build-android-app.yml
index 5299d72f..bf72a262 100644
--- a/.github/workflows/_build-android-app.yml
+++ b/.github/workflows/_build-android-app.yml
@@ -2,27 +2,30 @@ name: Android Build workflow
on:
workflow_call:
inputs:
- pubspec-filename:
+ pubspec_filename:
required: true
type: string
- environment-flag:
+ environment_flag:
required: true
type: string
versionNumber:
required: true
type: string
- build-flag:
+ build_flag:
required: true
type: boolean
- build-artifact:
+ build_artifact:
required: true
type: string
+ build_appbundle:
+ required: true
+ type: boolean
jobs:
- buildAndroid:
- name: Build Android
- if: ${{ inputs.build-flag == true }}
+ testAnalyzeBuildAndroid:
+ name: Android - Test, Analyze, Build
runs-on: ubuntu-latest
+
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -32,34 +35,74 @@ jobs:
- name: Set up Flutter and Java
uses: ./.github/actions/setup-java-flutter
+ - name: melos test all
+ run: melos test:all
+
+ - run: echo "Starting analyze"
+
+ - name: ⚠️ℹ️ Run Dart analysis
+ uses: zgosalvez/github-actions-analyze-dart@v3
+ with:
+ working-directory: "${{github.workspace}}/"
+
+ - run: echo "Starting build process"
+
- name: Get pubspec.yaml
+ if: ${{ inputs.build_flag == true }} && success()
uses: actions/download-artifact@v4
with:
- name: ${{ inputs.pubspec-filename }}
+ name: ${{ inputs.pubspec_filename }}
path: ${{ github.workspace }}/apps/multichoice/
- name: Download Android keystore
id: android_keystore
+ if: success()
uses: timheuer/base64-to-file@v1.2.4
with:
fileName: upload-keystore.jks
encodedString: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
- name: Create key.properties
- if: ${{ inputs.environment-flag }} == 'release'
+ if: ${{ inputs.environment_flag }} == 'release'
run: |
echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > key.properties
echo "storePassword=${{ secrets.STORE_PASSWORD }}" >> key.properties
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> key.properties
echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> key.properties
- working-directory: apps/multichoice/android
+ working-directory: apps/multichoice/android/
+
+ - name: Create secrets.dart
+ run: |
+ mkdir auth
+ echo "String webApiKey = '${{ secrets.WEB_API_KEY }}';" > auth/secrets.dart
+ echo "String webAppId = '${{ secrets.WEB_APP_ID }}';" >> auth/secrets.dart
+ echo "String androidApiKey = '${{ secrets.ANDROID_API_KEY }}';" >> auth/secrets.dart
+ echo "String androidAppId = '${{ secrets.ANDROID_APP_ID }}';" >> auth/secrets.dart
+ echo "String iosApiKey = '${{ secrets.IOS_API_KEY }}';" >> auth/secrets.dart
+ echo "String iosAppId = '${{ secrets.IOS_APP_ID }}';" >> auth/secrets.dart
+ working-directory: apps/multichoice/lib/
- - name: Start ${{ inputs.environment-flag }} build
- run: flutter build appbundle --${{ inputs.environment-flag }}
- working-directory: ./apps/multichoice
+ - name: Start appbundle ${{ inputs.environment_flag }} build
+ if: ${{ inputs.build_appbundle == true }} && success()
+ run: flutter build appbundle --${{ inputs.environment_flag }}
+ working-directory: apps/multichoice/
- - name: Upload Android ${{ inputs.environment-flag }}
+ - name: Start apk ${{ inputs.environment_flag }} build
+ if: success()
+ run: flutter build apk --${{ inputs.environment_flag }}
+ working-directory: apps/multichoice/
+
+ - name: Upload Android ${{ inputs.environment_flag }} appbundle
uses: actions/upload-artifact@v4
with:
- name: ${{ inputs.build-artifact }}
- path: ./apps/multichoice/build/app/outputs/bundle/release/app-release.aab
+ name: ${{ inputs.build_artifact }}
+ path: ./apps/multichoice/build/app/outputs/bundle/${{ inputs.environment_flag }}/app-${{ inputs.environment_flag }}.aab
+
+ - name: Upload artifact to Firebase App Distribution
+ if: success()
+ uses: wzieba/Firebase-Distribution-Github-Action@v1
+ with:
+ appId: ${{secrets.APP_ID}}
+ serviceCredentialsFileContent: ${{secrets.CREDENTIAL_FILE_CONTENT}}
+ groups: testers
+ file: ./apps/multichoice/build/app/outputs/flutter-apk/app-${{ inputs.environment_flag }}.apk
diff --git a/.github/workflows/_build-env-apps.yml b/.github/workflows/_build-env-apps.yml
index 8fb85832..9ee81018 100644
--- a/.github/workflows/_build-env-apps.yml
+++ b/.github/workflows/_build-env-apps.yml
@@ -2,31 +2,34 @@ name: Build Android, Web, & Windows Apps
on:
workflow_call:
inputs:
- pubspec-filename:
+ pubspec_filename:
required: true
type: string
- web-build-flag:
+ web_build_flag:
required: true
type: boolean
- web-environment-flag:
+ web_environment_flag:
required: true
type: string
- android-build-flag:
+ android_build_flag:
required: true
type: boolean
- android-environment-flag:
+ android_environment_flag:
required: true
type: string
- android-versionNumber:
+ android_versionNumber:
required: true
type: string
- android-build-artifact:
+ android_build_artifact:
required: true
type: string
- windows-build-flag:
+ android_build_appbundle:
required: true
type: boolean
- windows-environment-flag:
+ windows_build_flag:
+ required: true
+ type: boolean
+ windows_environment_flag:
required: true
type: string
@@ -35,23 +38,24 @@ jobs:
name: "Build Web app"
uses: ./.github/workflows/_build-web-app.yml
with:
- pubspec-filename: ${{ inputs.pubspec-filename }}
- environment-flag: ${{ inputs.web-environment-flag }}
- build-flag: ${{ inputs.web-build-flag }}
+ pubspec_filename: ${{ inputs.pubspec_filename }}
+ environment_flag: ${{ inputs.web_environment_flag }}
+ build_flag: ${{ inputs.web_build_flag }}
buildAndroid:
name: "Build Android app"
uses: ./.github/workflows/_build-android-app.yml
secrets: inherit
with:
- pubspec-filename: ${{ inputs.pubspec-filename }}
- environment-flag: ${{ inputs.android-environment-flag }}
- versionNumber: ${{ inputs.android-versionNumber }}
- build-flag: ${{ inputs.android-build-flag }}
- build-artifact: ${{ inputs.android-build-artifact }}
+ pubspec_filename: ${{ inputs.pubspec_filename }}
+ environment_flag: ${{ inputs.android_environment_flag }}
+ versionNumber: ${{ inputs.android_versionNumber }}
+ build_flag: ${{ inputs.android_build_flag }}
+ build_artifact: ${{ inputs.android_build_artifact }}
+ build_appbundle: ${{ inputs.android_build_appbundle }}
buildWindows:
name: "Build Windows app"
uses: ./.github/workflows/_build-windows-app.yml
with:
- pubspec-filename: ${{ inputs.pubspec-filename }}
- environment-flag: ${{ inputs.windows-environment-flag }}
- build-flag: ${{ inputs.windows-build-flag }}
+ pubspec_filename: ${{ inputs.pubspec_filename }}
+ environment_flag: ${{ inputs.windows_environment_flag }}
+ build_flag: ${{ inputs.windows_build_flag }}
diff --git a/.github/workflows/_build-web-app.yml b/.github/workflows/_build-web-app.yml
index 24cf517e..76f59c8d 100644
--- a/.github/workflows/_build-web-app.yml
+++ b/.github/workflows/_build-web-app.yml
@@ -2,20 +2,20 @@ name: Web Build workflow
on:
workflow_call:
inputs:
- pubspec-filename:
+ pubspec_filename:
required: true
type: string
- environment-flag:
+ environment_flag:
required: true
type: string
- build-flag:
+ build_flag:
required: true
type: boolean
jobs:
buildWeb:
name: Build Web
- if: inputs.build-flag == true
+ if: ${{ inputs.build_flag == true }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -25,8 +25,8 @@ jobs:
uses: ./.github/actions/setup-flutter
- name: Start Web Release Build
- run: flutter build web --base-href='/multichoice/' --${{ inputs.environment-flag }}
- working-directory: ./apps/multichoice
+ run: flutter build web --base-href='/multichoice/' --${{ inputs.environment_flag }}
+ working-directory: apps/multichoice/
- name: Upload Web Build Files
uses: actions/upload-artifact@v4
diff --git a/.github/workflows/_build-windows-app.yml b/.github/workflows/_build-windows-app.yml
index 291768a3..dc82bec1 100644
--- a/.github/workflows/_build-windows-app.yml
+++ b/.github/workflows/_build-windows-app.yml
@@ -2,20 +2,20 @@ name: Windows Build workflow
on:
workflow_call:
inputs:
- pubspec-filename:
+ pubspec_filename:
required: true
type: string
- environment-flag:
+ environment_flag:
required: true
type: string
- build-flag:
+ build_flag:
required: true
type: boolean
jobs:
buildWindows:
name: Build Windows
- if: ${{ inputs.build-flag == true }}
+ if: ${{ inputs.build_flag == true }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
diff --git a/.github/workflows/_deploy-android-app.yml b/.github/workflows/_deploy-android-app.yml
index 8ce41c0a..3aea8aa9 100644
--- a/.github/workflows/_deploy-android-app.yml
+++ b/.github/workflows/_deploy-android-app.yml
@@ -6,19 +6,19 @@ on:
deploy_flag:
required: true
type: boolean
- environment-flag:
+ environment_flag:
required: true
type: string
- package-name:
+ package_name:
required: true
type: string
track:
required: true
type: string
- release-name:
+ release_name:
required: true
type: string
- deploy-status:
+ deploy_status:
required: true
type: string
@@ -47,9 +47,9 @@ jobs:
with:
releaseFiles: app-release.aab
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
- packageName: ${{ inputs.package-name }}
+ packageName: ${{ inputs.package_name }}
track: ${{ inputs.track }}
- releaseName: ${{ inputs.release-name }}
+ releaseName: ${{ inputs.release_name }}
# inAppUpdatePriority: 2
# userFraction: 0.5
- status: ${{ inputs.deploy-status }}
+ status: ${{ inputs.deploy_status }}
diff --git a/.github/workflows/_deploy-env-apps.yml b/.github/workflows/_deploy-env-apps.yml
index 104a3605..3d332b6a 100644
--- a/.github/workflows/_deploy-env-apps.yml
+++ b/.github/workflows/_deploy-env-apps.yml
@@ -5,33 +5,33 @@ on:
web_deploy_flag:
required: true
type: boolean
- web-environment-flag:
+ web_environment_flag:
required: true
type: string
android_deploy_flag:
required: true
type: boolean
- android-environment-flag:
+ android_environment_flag:
required: true
type: string
- android-package-name:
+ android_package_name:
required: true
type: string
- android-track:
+ android_track:
required: true
type: string
- android-release-name:
+ android_release_name:
required: true
type: string
- android-deploy-status:
+ android_deploy_status:
required: true
type: string
windows_deploy_flag:
required: true
type: boolean
- windows-environment-flag:
+ windows_environment_flag:
required: true
type: string
@@ -46,21 +46,21 @@ jobs:
secrets: inherit
with:
deploy_flag: ${{ inputs.web_deploy_flag }}
- environment-flag: ${{ inputs.web-environment-flag }}
+ environment_flag: ${{ inputs.web_environment_flag }}
deployAndroid:
name: "Deploy Android app"
uses: ./.github/workflows/_deploy-android-app.yml
secrets: inherit
with:
deploy_flag: ${{ inputs.android_deploy_flag }}
- environment-flag: ${{ inputs.android-environment-flag }}
- package-name: ${{ inputs.android-package-name }}
- track: ${{ inputs.android-track }}
- release-name: ${{ inputs.android-release-name }}
- deploy-status: ${{ inputs.android-deploy-status }}
+ environment_flag: ${{ inputs.android_environment_flag }}
+ package_name: ${{ inputs.android_package_name }}
+ track: ${{ inputs.android_track }}
+ release_name: ${{ inputs.android_release_name }}
+ deploy_status: ${{ inputs.android_deploy_status }}
deployWindows:
name: "Deploy Windows app"
uses: ./.github/workflows/_deploy-windows-app.yml
with:
deploy_flag: ${{ inputs.windows_deploy_flag }}
- environment-flag: ${{ inputs.windows-environment-flag }}
+ environment_flag: ${{ inputs.windows_environment_flag }}
diff --git a/.github/workflows/_deploy-web-app.yml b/.github/workflows/_deploy-web-app.yml
index 1bb96ac2..f20a50f2 100644
--- a/.github/workflows/_deploy-web-app.yml
+++ b/.github/workflows/_deploy-web-app.yml
@@ -6,7 +6,7 @@ on:
deploy_flag:
required: true
type: boolean
- environment-flag:
+ environment_flag:
required: true
type: string
workflow_dispatch:
diff --git a/.github/workflows/_deploy-windows-app.yml b/.github/workflows/_deploy-windows-app.yml
index 36a63cf4..5b32e7e5 100644
--- a/.github/workflows/_deploy-windows-app.yml
+++ b/.github/workflows/_deploy-windows-app.yml
@@ -6,7 +6,7 @@ on:
deploy_flag:
required: true
type: boolean
- environment-flag:
+ environment_flag:
required: true
type: string
workflow_dispatch:
diff --git a/.github/workflows/build_workflow.yml b/.github/workflows/build_workflow.yml
index 2a1407d4..baa03540 100644
--- a/.github/workflows/build_workflow.yml
+++ b/.github/workflows/build_workflow.yml
@@ -20,60 +20,9 @@ concurrency:
cancel-in-progress: true
jobs:
- test:
- name: Test
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Set up Flutter
- uses: ./.github/actions/setup-flutter
-
- - name: melos test all
- run: melos test:all
-
- analyze:
- name: Analyze
- if: github.event.pull_request.draft == false
- runs-on: ubuntu-latest
- needs: test
-
- permissions:
- contents: read
- packages: read
- statuses: write
-
- steps:
- - run: echo "Starting analyze"
-
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Set up Flutter
- uses: ./.github/actions/setup-flutter
-
- - name: ⚠️ℹ️ Run Dart analysis
- uses: zgosalvez/github-actions-analyze-dart@v3
- with:
- working-directory: "${{github.workspace}}/"
-
- # - name: Super-linter
- # uses: super-linter/super-linter@v6.0.0
- # env:
- # DEFAULT_BRANCH: main
- # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # IGNORE_GITIGNORED_FILES: true
- # FILTER_REGEX_EXCLUDE: "\\.freezed\\.dart$"
- # CREATE_LOG_FILE: true
- # DISABLE_ERRORS: true
-
preBuild:
- name: Bump build number
+ name: Prebuild - Bump build number
+ if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
concurrency:
group: build-group
@@ -83,6 +32,10 @@ jobs:
web_build_flag: ${{ steps.id_out.outputs.web_build_flag }}
android_build_flag: ${{ steps.id_out.outputs.android_build_flag }}
windows_build_flag: ${{ steps.id_out.outputs.windows_build_flag }}
+
+ web_environment_flag: ${{ steps.id_out.outputs.web_environment_flag }}
+ android_environment_flag: ${{ steps.id_out.outputs.android_environment_flag }}
+ windows_environment_flag: ${{ steps.id_out.outputs.windows_environment_flag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -108,6 +61,10 @@ jobs:
echo "android_build_flag=${{ steps.read_config_yaml.outputs.build__android_build_flag }}" >> $GITHUB_OUTPUT
echo "windows_build_flag=${{ steps.read_config_yaml.outputs.build__windows_build_flag }}" >> $GITHUB_OUTPUT
+ echo "web_environment_flag=${{ steps.read_config_yaml.outputs.environment__web_environment_flag }}" >> $GITHUB_OUTPUT
+ echo "android_environment_flag=${{ steps.read_config_yaml.outputs.environment__android_environment_flag }}" >> $GITHUB_OUTPUT
+ echo "windows_environment_flag=${{ steps.read_config_yaml.outputs.environment__windows_environment_flag }}" >> $GITHUB_OUTPUT
+
- name: Get pubspec.yaml version
uses: actions/download-artifact@v4
with:
@@ -118,19 +75,23 @@ jobs:
name: Build Apps
if: github.event.pull_request.draft == false && success()
uses: ./.github/workflows/_build-env-apps.yml
- needs: [analyze, preBuild]
+ needs: [preBuild]
secrets: inherit
+ permissions:
+ contents: read
+ packages: read
+ statuses: write
with:
- pubspec-filename: "pubspec-file"
- web-build-flag: ${{ needs.preBuild.outputs.web_build_flag == 'true' }}
- web-environment-flag: "release"
- # profile, release
- android-build-flag: ${{ needs.preBuild.outputs.android_build_flag == 'true' }}
- android-environment-flag: "release"
- android-versionNumber: ${{ needs.preBuild.outputs.version_number }}
- android-build-artifact: "android-release"
- windows-build-flag: ${{ needs.preBuild.outputs.windows_build_flag == 'true' }}
- windows-environment-flag: "release"
+ pubspec_filename: "pubspec-file"
+ web_build_flag: ${{ needs.preBuild.outputs.web_build_flag == 'true' }}
+ web_environment_flag: ${{ needs.preBuild.outputs.web_environment_flag }}
+ android_build_flag: ${{ needs.preBuild.outputs.android_build_flag == 'true' }}
+ android_environment_flag: ${{ needs.preBuild.outputs.android_environment_flag }}
+ android_versionNumber: ${{ needs.preBuild.outputs.version_number }}
+ android_build_artifact: "android-release"
+ android_build_appbundle: ${{ 'false' == 'true' }}
+ windows_build_flag: ${{ needs.preBuild.outputs.windows_build_flag == 'true' }}
+ windows_environment_flag: ${{ needs.preBuild.outputs.windows_environment_flag }}
post-build:
name: Post Build
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 9f39e7e8..8b252305 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -41,13 +41,13 @@ jobs:
run: flutter build web --base-href='/multichoice/' --release
- name: Install GitVersion
- uses: gittools/actions/gitversion/setup@v0.13.4
+ uses: gittools/actions/gitversion/setup@v1.1.1
with:
versionSpec: "5.x"
- name: Use GitVersion
id: gitversion
- uses: gittools/actions/gitversion/execute@v0.13.4
+ uses: gittools/actions/gitversion/execute@v1.1.1
- name: Create version.txt with nuGetVersion
shell: bash
@@ -69,7 +69,7 @@ jobs:
- name: Update pubspec.yml version action
id: update-pubspec
- uses: stikkyapp/update-pubspec-version@v1
+ uses: stikkyapp/update-pubspec-version@v2
with:
strategy: "none"
# bump-build: true
diff --git a/.github/workflows/deploy_workflow.yml b/.github/workflows/deploy_workflow.yml
index 72f44739..97bd5475 100644
--- a/.github/workflows/deploy_workflow.yml
+++ b/.github/workflows/deploy_workflow.yml
@@ -20,6 +20,7 @@ jobs:
windows_deploy_flag: ${{ steps.id_out.outputs.windows_deploy_flag }}
android_package_name: ${{ steps.id_out.outputs.android_package_name }}
+ android_environment_url: ${{ steps.id_out.outputs.android_environment_url }}
android_track: ${{ steps.id_out.outputs.android_track }}
android_release_name: ${{ steps.id_out.outputs.android_release_name }}
android_deploy_status: ${{ steps.id_out.outputs.android_deploy_status }}
@@ -40,6 +41,7 @@ jobs:
echo "windows_deploy_flag=${{ steps.read_config_yaml.outputs.build__windows_build_flag }}" >> $GITHUB_OUTPUT
echo "android_package_name=${{ steps.read_config_yaml.outputs.android_deploy__android_package_name }}" >> $GITHUB_OUTPUT
+ echo "android_environment_url=${{ steps.read_config_yaml.outputs.android_deploy__android_environment_url }}" >> $GITHUB_OUTPUT
echo "android_track=${{ steps.read_config_yaml.outputs.android_deploy__android_track }}" >> $GITHUB_OUTPUT
echo "android_release_name=${{ steps.read_config_yaml.outputs.android_deploy__android_release_name }}" >> $GITHUB_OUTPUT
echo "android_deploy_status=${{ steps.read_config_yaml.outputs.android_deploy__android_deploy_status }}" >> $GITHUB_OUTPUT
@@ -54,15 +56,15 @@ jobs:
secrets: inherit
with:
web_deploy_flag: ${{ needs.preDeploy.outputs.web_deploy_flag == 'true' }}
- web-environment-flag: "Web Prod"
+ web_environment_flag: "Web Prod"
android_deploy_flag: ${{ needs.preDeploy.outputs.android_deploy_flag == 'true' }}
- android-environment-flag: "Android Prod"
- android-package-name: ${{ needs.preDeploy.outputs.android_package_name }}
- # TODO(ZK): add android environment url
- android-track: ${{ needs.preDeploy.outputs.android_track }}
- android-release-name: ${{ needs.preDeploy.outputs.android_release_name }}
- android-deploy-status: ${{ needs.preDeploy.outputs.android_deploy_status }}
+ android_environment_flag: "Android Prod"
+ android_package_name: ${{ needs.preDeploy.outputs.android_package_name }}
+ android_environment_url: ${{ needs.preDeploy.outputs.android_environment_url }}
+ android_track: ${{ needs.preDeploy.outputs.android_track }}
+ android_release_name: ${{ needs.preDeploy.outputs.android_release_name }}
+ android_deploy_status: ${{ needs.preDeploy.outputs.android_deploy_status }}
windows_deploy_flag: ${{ needs.preDeploy.outputs.windows_deploy_flag == 'true' }}
- windows-environment-flag: "Windows Prod"
+ windows_environment_flag: "Windows Prod"
diff --git a/.github/workflows/release_workflow.yml b/.github/workflows/release_workflow.yml
index bd0b6a2e..1239a5c3 100644
--- a/.github/workflows/release_workflow.yml
+++ b/.github/workflows/release_workflow.yml
@@ -109,23 +109,23 @@ jobs:
name: pubspec-file-release
path: ${{ github.workspace }}/apps/multichoice/
- build:
- name: Build Apps
+ buildRelease:
+ name: Build Apps for Release
if: github.event.pull_request.draft == false
uses: ./.github/workflows/_build-env-apps.yml
needs: [bumpVersion]
secrets: inherit
with:
- pubspec-filename: "pubspec-file-release"
- web-build-flag: ${{ needs.bumpVersion.outputs.web_build_flag == 'true' }}
- web-environment-flag: "release"
- # profile, release
- android-build-flag: ${{ needs.bumpVersion.outputs.android_build_flag == 'true' }}
- android-environment-flag: "release"
- android-versionNumber: ${{ needs.bumpVersion.outputs.version_number }}
- android-build-artifact: "android-release"
- windows-build-flag: ${{ needs.bumpVersion.outputs.windows_build_flag == 'true' }}
- windows-environment-flag: "release"
+ pubspec_filename: "pubspec-file-release"
+ web_build_flag: ${{ needs.bumpVersion.outputs.web_build_flag == 'true' }}
+ web_environment_flag: ${{ needs.bumpVersion.outputs.web_environment_flag }}
+ android_build_flag: ${{ needs.bumpVersion.outputs.android_build_flag == 'true' }}
+ android_environment_flag: ${{ needs.bumpVersion.outputs.android_environment_flag }}
+ android_versionNumber: ${{ needs.bumpVersion.outputs.version_number }}
+ android_build_artifact: "android-release"
+ android_build_appbundle: ${{ 'true' == 'true' }}
+ windows_build_flag: ${{ needs.bumpVersion.outputs.windows_build_flag == 'true' }}
+ windows_environment_flag: ${{ needs.bumpVersion.outputs.windows_environment_flag }}
commitBump:
name: Auto Commit Bumped Version
diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml
deleted file mode 100644
index 391f9ae7..00000000
--- a/.github/workflows/setup.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: Common Setup
-
-on:
- # workflow_call:
- # push:
- # branches:
- # - fix-workflows
- workflow_dispatch:
-
-jobs:
- setup:
- name: Set up Java and Flutter
- runs-on: ubuntu-latest
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: read-config-file
- uses: pietrobolcato/action-read-yaml@1.1.0
- id: read_config_yaml
- with:
- config: ${{ github.workspace }}/config.yml
-
- - id: echo_config
- run: |
- echo "draft: ${{ steps.read_config_yaml.outputs['release.draft'] }}"
- echo "prerelease: ${{ steps.read_config_yaml.outputs['release.prerelease'] }}"
-
- # - id: read_config
- # uses: actions/github-script@v7
- # with:
- # github-token: ${{ secrets.GITHUB_TOKEN }}
- # script: |
- # const yaml = require('js-yaml');
- # const fs = require('fs');
- # const path = require('path');
-
- # const configFile = path.join(process.env.GITHUB_WORKSPACE, 'config.yml');
- # const config = yaml.load(fs.readFileSync(configFile, 'utf8'));
-
- # console.log(JSON.stringify(config));
- # const draft = process.env.CONFIG_RELEASE_DRAFT = config.release.draft ? 'true' : 'false';
- # const prerelease = process.env.CONFIG_RELEASE_PRERELEASE = config.release.prerelease ? 'true' : 'false';
-
- # console.log("${CONFIG_RELEASE_DRAFT}, ${CONFIG_RELEASE_PRERELEASE}");
-
- # return draft.concat(':', prerelease)
-
- # result-encoding: string
diff --git a/.gitignore b/.gitignore
index b059cbf9..8348339a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,7 @@ pubspec.lock
**/*.g.dart
**/*.config.dart
**/*.auto_mappr.dart
+**/*.tailor.dart
**/*.gr.dart
**/*.isar
**/*.isar.lock
diff --git a/.metadata b/.metadata
index 121df5a6..b7b051e6 100644
--- a/.metadata
+++ b/.metadata
@@ -35,5 +35,5 @@ migration:
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- - 'lib/main.dart'
- - 'ios/Runner.xcodeproj/project.pbxproj'
+ - "lib/main.dart"
+ - "ios/Runner.xcodeproj/project.pbxproj"
diff --git a/android/app/src/main/kotlin/co/za/zanderkotze/multichoice/MainActivity.kt b/android/app/src/main/kotlin/co/za/zanderkotze/multichoice/MainActivity.kt
new file mode 100644
index 00000000..55b328f6
--- /dev/null
+++ b/android/app/src/main/kotlin/co/za/zanderkotze/multichoice/MainActivity.kt
@@ -0,0 +1,5 @@
+package co.za.zanderkotze.multichoice
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/apps/multichoice/.gitignore b/apps/multichoice/.gitignore
index a09c194d..49e5a445 100644
--- a/apps/multichoice/.gitignore
+++ b/apps/multichoice/.gitignore
@@ -19,6 +19,7 @@ pubspec.lock
**/*.isar.lock
**/libisar.dylib
**/coverage
+**/firebase_options.dart
# Keys and Secrets
password.txt
@@ -26,10 +27,11 @@ upload-keystore.*
**/key.properties
**/multichoice-*.json
**/**/.ssh
+**/lib/auth/secrets.dart
# dotenv environment variables file
.env*
-**/tmp.*
+**/tmp*
**/tmp/
# Avoid committing generated Javascript files:
diff --git a/apps/multichoice/analysis_options.yaml b/apps/multichoice/analysis_options.yaml
index 0e469662..5b7264de 100644
--- a/apps/multichoice/analysis_options.yaml
+++ b/apps/multichoice/analysis_options.yaml
@@ -4,3 +4,7 @@ linter:
rules:
public_member_api_docs: false
lines_longer_than_80_chars: false
+
+analyzer:
+ exclude:
+ - lib/firebase_options.dart
diff --git a/apps/multichoice/android/app/build.gradle b/apps/multichoice/android/app/build.gradle
index ed41a51b..51281b56 100644
--- a/apps/multichoice/android/app/build.gradle
+++ b/apps/multichoice/android/app/build.gradle
@@ -2,6 +2,7 @@ plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
+ // id 'com.google.gms.google-services'
}
def localProperties = new Properties()
@@ -67,6 +68,8 @@ android {
}
buildTypes {
release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
ndk {
debugSymbolLevel 'FULL'
@@ -79,4 +82,7 @@ flutter {
source '../..'
}
-dependencies {}
+dependencies {
+ // implementation platform('com.google.firebase:firebase-bom:32.7.3')
+ // implementation 'com.google.firebase:firebase-analytics'
+}
diff --git a/apps/multichoice/android/app/src/main/AndroidManifest.xml b/apps/multichoice/android/app/src/main/AndroidManifest.xml
index d6b14ab0..0e6806f5 100644
--- a/apps/multichoice/android/app/src/main/AndroidManifest.xml
+++ b/apps/multichoice/android/app/src/main/AndroidManifest.xml
@@ -1,28 +1,36 @@
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
-
+
\ No newline at end of file
diff --git a/apps/multichoice/ios/.gitignore b/apps/multichoice/ios/.gitignore
index 7a7f9873..1e1726c9 100644
--- a/apps/multichoice/ios/.gitignore
+++ b/apps/multichoice/ios/.gitignore
@@ -32,3 +32,7 @@ Runner/GeneratedPluginRegistrant.*
!default.mode2v3
!default.pbxuser
!default.perspectivev3
+
+# Generated code and files
+**/firebase_app_id_file.json
+**/GoogleService-Info.plist
\ No newline at end of file
diff --git a/apps/multichoice/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/apps/multichoice/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 919434a6..337ef66e 100644
--- a/apps/multichoice/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/apps/multichoice/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -1,7 +1,7 @@
+ version="1.0">
+ location="self:">
-
+
\ No newline at end of file
diff --git a/apps/multichoice/lib/app/extensions/extension_getters.dart b/apps/multichoice/lib/app/extensions/extension_getters.dart
new file mode 100644
index 00000000..a01b7111
--- /dev/null
+++ b/apps/multichoice/lib/app/extensions/extension_getters.dart
@@ -0,0 +1,10 @@
+import 'package:flutter/material.dart';
+
+extension ThemeGetter on BuildContext {
+ // Usage example: `context.theme`
+ ThemeData get theme => Theme.of(this);
+}
+
+// extension SizeGetter on BuildContext {
+// Size get size => MediaQuery.of(this).size;
+// }
diff --git a/apps/multichoice/lib/app/view/app.dart b/apps/multichoice/lib/app/view/app.dart
index cf6a50b8..c2175b7f 100644
--- a/apps/multichoice/lib/app/view/app.dart
+++ b/apps/multichoice/lib/app/view/app.dart
@@ -1,5 +1,9 @@
+import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:multichoice/app/engine/app_router.dart';
+import 'package:multichoice/app/view/theme/app_theme.dart';
+import 'package:provider/provider.dart';
+import 'package:shared_preferences/shared_preferences.dart';
class App extends StatelessWidget {
App({super.key});
@@ -8,17 +12,24 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return MaterialApp.router(
- title: 'Multichoice',
- theme: ThemeData(
- colorScheme: ColorScheme.fromSeed(
- seedColor: Colors.deepPurple,
- ),
- useMaterial3: true,
+ final sharedPref = coreSl();
+ final systemBrightness = MediaQuery.platformBrightnessOf(context);
+ final isDarkMode = systemBrightness == Brightness.dark;
+
+ if (sharedPref.getString('theme') == 'light' && isDarkMode) {
+ sharedPref.setString('theme', 'dark');
+ }
+
+ return ChangeNotifierProvider(
+ create: (context) => AppTheme(),
+ builder: (context, child) => MaterialApp.router(
+ title: 'Multichoice',
+ theme: AppTheme.light,
+ darkTheme: AppTheme.dark,
+ themeMode: context.watch().themeMode,
+ debugShowCheckedModeBanner: false,
+ routerConfig: _appRouter.config(),
),
- darkTheme: ThemeData.dark(),
- debugShowCheckedModeBanner: false,
- routerConfig: _appRouter.config(),
);
}
}
diff --git a/apps/multichoice/lib/app/view/theme/app_palette.dart b/apps/multichoice/lib/app/view/theme/app_palette.dart
new file mode 100644
index 00000000..c843bc1e
--- /dev/null
+++ b/apps/multichoice/lib/app/view/theme/app_palette.dart
@@ -0,0 +1,66 @@
+import 'package:flutter/material.dart';
+
+abstract class AppPalette {
+ static const white = Color(0xffffffff);
+ static const black = Color(0xff000000);
+
+ static const red = Colors.red;
+ static const imperialRed = Color(0xFFE54B4B);
+
+ static const seashell = Color(0xFFF7EBE8);
+
+ static final green = _GreenColors();
+ static final grey = _GreyColors();
+ static final paletteOne = _PaletteOne();
+ static final paletteTwo = _PaletteTwo();
+}
+
+class _GreenColors {
+ _GreenColors();
+
+ final greenPea = const Color(0xff26734d);
+ final chateauGreen = const Color(0xff37a06b);
+ final silverTree = const Color(0xff4eb18b);
+ final neptune = const Color(0xff81c1aa);
+ final jetStream = const Color(0xffa8d1c8);
+}
+
+class _GreyColors {
+ _GreyColors();
+
+ final bigStone = const Color(0xff15203c);
+ final sanJuan = const Color(0xff385170);
+ final sanJuanLight = const Color(0x7f385170);
+ final slateGray = const Color(0xff697a91);
+ final towerGray = const Color(0xffa1baba);
+ final geyser = const Color(0xffd7e0e5); // primary
+ final geyserLight = const Color(0x7fd7e0e5);
+}
+
+class _PaletteTwo {
+ _PaletteTwo();
+
+ final primary0 = const Color(0xff050a19);
+ final primary5 = const Color(0xff121625);
+ final primary10 = const Color(0xff8995C1);
+ final primary15 = const Color(0xff2A2F3B);
+ final primary20 = const Color(0xff373B47);
+
+ final bigStone = const Color(0xff15203C); // primary 171d2c
+ final bigStoneLight = const Color(0xff313B54);
+ final sanJuan = const Color(0xffe7ecef);
+ final sanJuanLight = const Color(0x7f172435);
+ final slateGray = const Color(0xff79899D); // 555d68
+ final slateGrayLight = const Color(0x7f555d68);
+ final towerGray = const Color(0xff4e5c5c);
+ final geyser = const Color(0xffDBE3E8); // 172435
+ final geyserLight = const Color(0x7f172435);
+}
+
+class _PaletteOne {
+ final darkSpringGreen = const Color(0xff226f54);
+ final pistachio = const Color(0xff87c38f);
+ final lemonChiffon = const Color(0xfff4f0bb);
+ final black = const Color(0xff000000);
+ final quinacridoneMagenta = const Color(0xff8f2d56);
+}
diff --git a/apps/multichoice/lib/app/view/theme/app_theme.dart b/apps/multichoice/lib/app/view/theme/app_theme.dart
new file mode 100644
index 00000000..5279b5fb
--- /dev/null
+++ b/apps/multichoice/lib/app/view/theme/app_theme.dart
@@ -0,0 +1,292 @@
+import 'package:flutter/material.dart';
+import 'package:multichoice/app/view/theme/app_palette.dart';
+import 'package:multichoice/app/view/theme/app_typography.dart';
+import 'package:multichoice/constants/export_constants.dart';
+import 'package:theme/theme.dart';
+
+class AppTheme with ChangeNotifier {
+ ThemeMode _themeMode = ThemeMode.system;
+
+ ThemeMode get themeMode => _themeMode;
+
+ set themeMode(ThemeMode themeMode) {
+ _themeMode = themeMode;
+ notifyListeners();
+ }
+
+ static final light = () {
+ final defaultTheme = ThemeData.light();
+
+ return defaultTheme.copyWith(
+ outlinedButtonTheme: OutlinedButtonThemeData(
+ style: OutlinedButton.styleFrom(
+ foregroundColor: _lightAppColors.primary,
+ textStyle: TextStyle(color: _lightAppColors.background),
+ side: BorderSide(color: _lightAppColors.primary ?? Colors.white),
+ shape: RoundedRectangleBorder(borderRadius: borderCircular12),
+ minimumSize: outlinedButtonMinimumSize,
+ ),
+ ),
+ textSelectionTheme: const TextSelectionThemeData(
+ cursorColor: Colors.white,
+ selectionHandleColor: Colors.grey,
+ ),
+ textButtonTheme: TextButtonThemeData(
+ style: ButtonStyle(
+ foregroundColor: MaterialStatePropertyAll(
+ _lightAppColors.black,
+ ),
+ backgroundColor: MaterialStatePropertyAll(
+ AppPalette.grey.geyserLight,
+ ),
+ ),
+ ),
+ elevatedButtonTheme: ElevatedButtonThemeData(
+ style: ElevatedButton.styleFrom(
+ foregroundColor: _lightAppColors.secondary,
+ shape: RoundedRectangleBorder(borderRadius: borderCircular12),
+ minimumSize: elevatedButtonMinimumSize,
+ ),
+ ),
+ dialogBackgroundColor: _lightAppColors.background,
+ dialogTheme: DialogTheme(
+ shape: RoundedRectangleBorder(borderRadius: borderCircular16),
+ alignment: Alignment.center,
+ titleTextStyle: AppTypography.titleMedium,
+ contentTextStyle: AppTypography.bodyMedium,
+ actionsPadding: allPadding12,
+ ),
+ scaffoldBackgroundColor: _lightAppColors.background,
+ appBarTheme: AppBarTheme(
+ titleTextStyle: AppTypography.titleMedium.copyWith(
+ color: AppPalette.grey.geyser,
+ ),
+ centerTitle: true,
+ color: _lightAppColors.foreground,
+ ),
+ cardTheme: CardTheme(
+ margin: vertical12horizontal4,
+ elevation: 7,
+ shadowColor: Colors.white,
+ shape: RoundedRectangleBorder(borderRadius: borderCircular12),
+ ),
+ textTheme: defaultTheme.textTheme.copyWith(
+ titleMedium: _lightTextTheme.titleMedium,
+ bodyMedium: _lightTextTheme.bodyMedium,
+ ),
+ inputDecorationTheme: InputDecorationTheme(
+ labelStyle: const TextStyle(color: AppPalette.white),
+ hintStyle: const TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ ),
+ floatingLabelStyle: const TextStyle(color: Colors.white),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: borderCircular8,
+ borderSide: const BorderSide(color: Colors.white),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: borderCircular8,
+ borderSide: const BorderSide(color: Colors.white),
+ ),
+ ),
+ iconButtonTheme: IconButtonThemeData(
+ style: ButtonStyle(
+ foregroundColor: MaterialStatePropertyAll(AppPalette.grey.geyser),
+ padding: const MaterialStatePropertyAll(EdgeInsets.zero),
+ side: const MaterialStatePropertyAll(BorderSide.none),
+ ),
+ ),
+ iconTheme: IconThemeData(
+ size: 18,
+ color: _lightAppColors.primary,
+ ),
+ extensions: [
+ _lightAppColors,
+ _lightTextTheme,
+ ],
+ );
+ }();
+
+ static final _lightAppColors = AppColorsExtension(
+ primary: AppPalette.grey.geyser,
+ primaryLight: AppPalette.grey.geyserLight,
+ secondary: AppPalette.grey.sanJuan,
+ secondaryLight: AppPalette.grey.sanJuanLight,
+ ternary: AppPalette.grey.bigStone,
+ foreground: AppPalette.grey.bigStone,
+ background: AppPalette.grey.slateGray,
+ white: null,
+ black: AppPalette.black,
+ );
+
+ static final _lightTextTheme = AppTextExtension(
+ body1: AppTypography.body1.copyWith(color: _lightAppColors.background),
+ body2: AppTypography.body2,
+ h1: null,
+ titleLarge: null,
+ titleMedium: AppTypography.titleMedium.copyWith(
+ color: AppPalette.grey.bigStone,
+ ),
+ titleSmall: AppTypography.titleSmall.copyWith(
+ color: AppPalette.grey.geyser,
+ ),
+ subtitleLarge: null,
+ subtitleMedium: AppTypography.subtitleMedium.copyWith(
+ color: AppPalette.grey.bigStone,
+ ),
+ subtitleSmall: AppTypography.subtitleSmall.copyWith(
+ color: AppPalette.grey.geyser,
+ ),
+ bodyLarge: AppTypography.bodyLarge,
+ bodyMedium: AppTypography.bodyMedium.copyWith(
+ color: AppPalette.grey.geyser,
+ ),
+ bodySmall: null,
+ );
+
+ static final dark = () {
+ final defaultTheme = ThemeData.dark();
+
+ return defaultTheme.copyWith(
+ outlinedButtonTheme: OutlinedButtonThemeData(
+ style: OutlinedButton.styleFrom(
+ foregroundColor: _darkAppColors.white,
+ textStyle: AppTypography.bodyLarge,
+ side: BorderSide(
+ color: _darkAppColors.white ?? Colors.white,
+ ),
+ shape: RoundedRectangleBorder(borderRadius: borderCircular12),
+ minimumSize: outlinedButtonMinimumSize,
+ ),
+ ),
+ elevatedButtonTheme: ElevatedButtonThemeData(
+ style: ElevatedButton.styleFrom(
+ foregroundColor: AppPalette.paletteTwo.primary0,
+ backgroundColor: AppPalette.white,
+ shape: RoundedRectangleBorder(borderRadius: borderCircular12),
+ textStyle: AppTypography.bodyLarge,
+ minimumSize: elevatedButtonMinimumSize,
+ ),
+ ),
+ dialogBackgroundColor: _darkAppColors.background,
+ dialogTheme: DialogTheme(
+ surfaceTintColor: Colors.transparent,
+ shape: RoundedRectangleBorder(borderRadius: borderCircular16),
+ alignment: Alignment.center,
+ titleTextStyle: AppTypography.titleMedium,
+ contentTextStyle: AppTypography.bodyMedium,
+ actionsPadding: allPadding12,
+ ),
+ scaffoldBackgroundColor: _darkAppColors.background,
+ appBarTheme: AppBarTheme(
+ titleTextStyle: AppTypography.titleMedium.copyWith(
+ color: AppPalette.paletteTwo.sanJuan,
+ ),
+ centerTitle: true,
+ color: _darkAppColors.foreground,
+ ),
+ cardTheme: CardTheme(
+ margin: vertical12horizontal4,
+ elevation: 7,
+ shadowColor: _darkAppColors.black,
+ shape: RoundedRectangleBorder(borderRadius: borderCircular12),
+ ),
+ textTheme: defaultTheme.textTheme.copyWith(
+ titleMedium: _darkTextTheme.titleMedium,
+ bodyMedium: _darkTextTheme.bodyMedium,
+ ),
+ iconButtonTheme: IconButtonThemeData(
+ style: ButtonStyle(
+ foregroundColor:
+ MaterialStatePropertyAll(AppPalette.paletteTwo.sanJuan),
+ padding: const MaterialStatePropertyAll(EdgeInsets.zero),
+ side: const MaterialStatePropertyAll(BorderSide.none),
+ ),
+ ),
+ iconTheme: IconThemeData(
+ size: 18,
+ color: AppPalette.paletteTwo.sanJuan,
+ ),
+ inputDecorationTheme: InputDecorationTheme(
+ labelStyle: const TextStyle(color: AppPalette.white),
+ hintStyle: const TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ ),
+ floatingLabelStyle: const TextStyle(color: Colors.white),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: borderCircular8,
+ borderSide: const BorderSide(color: Colors.white),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: borderCircular8,
+ borderSide: const BorderSide(color: Colors.white),
+ ),
+ ),
+ textSelectionTheme: const TextSelectionThemeData(
+ cursorColor: Colors.white,
+ selectionHandleColor: Colors.grey,
+ ),
+ textButtonTheme: TextButtonThemeData(
+ style: ButtonStyle(
+ backgroundColor: MaterialStatePropertyAll(
+ AppPalette.grey.geyserLight,
+ ),
+ ),
+ ),
+ extensions: [
+ _darkAppColors,
+ _darkTextTheme,
+ ],
+ );
+ }();
+
+ static final _darkAppColors = AppColorsExtension(
+ // primary: AppPalette.paletteTwo.geyser,
+ primary: AppPalette.paletteTwo.primary5,
+ primaryLight: AppPalette.grey.geyserLight.withOpacity(0.2),
+ secondary: AppPalette.paletteTwo.primary10,
+ secondaryLight: AppPalette.paletteTwo.slateGrayLight,
+ ternary: AppPalette.paletteTwo.sanJuan,
+ foreground: AppPalette.paletteTwo.primary15,
+ background: AppPalette.paletteTwo.primary0,
+ white: AppPalette.white,
+ black: AppPalette.black,
+ );
+
+ static final _darkTextTheme = AppTextExtension(
+ body1: AppTypography.body1.copyWith(color: _darkAppColors.background),
+ body2: AppTypography.body2,
+ h1: null,
+ titleLarge: null,
+ titleMedium: AppTypography.titleMedium.copyWith(
+ color: AppPalette.paletteTwo.geyser,
+ ),
+ titleSmall: AppTypography.titleSmall.copyWith(
+ color: AppPalette.paletteTwo.primary5,
+ ),
+ subtitleLarge: null,
+ subtitleMedium: AppTypography.subtitleMedium.copyWith(
+ color: AppPalette.paletteTwo.geyser,
+ ),
+ subtitleSmall: AppTypography.subtitleMedium.copyWith(
+ color: AppPalette.paletteTwo.primary5,
+ ),
+ bodyLarge: AppTypography.bodyLarge.copyWith(
+ color: AppPalette.paletteTwo.primary5,
+ ),
+ bodyMedium: AppTypography.bodyMedium.copyWith(
+ color: AppPalette.paletteTwo.geyser,
+ ),
+ bodySmall: null,
+ );
+
+ static AppColorsExtension get lightAppColors => _lightAppColors;
+
+ static AppTextExtension get lightTextTheme => _lightTextTheme;
+
+ static AppColorsExtension get darkAppColors => _darkAppColors;
+
+ static AppTextExtension get darkTextTheme => _darkTextTheme;
+}
diff --git a/apps/multichoice/lib/app/view/theme/app_typography.dart b/apps/multichoice/lib/app/view/theme/app_typography.dart
new file mode 100644
index 00000000..91c7dced
--- /dev/null
+++ b/apps/multichoice/lib/app/view/theme/app_typography.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+
+abstract class AppTypography {
+ static const body1 = TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.normal,
+ );
+
+ static const body2 = TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.normal,
+ );
+
+ static const h1 = TextStyle(
+ fontSize: 96,
+ fontWeight: FontWeight.w300,
+ );
+
+ static const h2 = TextStyle(
+ fontSize: 72,
+ fontWeight: FontWeight.w500,
+ );
+
+ static const titleLarge = TextStyle(
+ fontSize: 36,
+ fontWeight: FontWeight.w400,
+ );
+ static const titleMedium = TextStyle(
+ fontSize: 22,
+ fontWeight: FontWeight.w500,
+ );
+ static const titleSmall = TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w500,
+ );
+ static const subtitleLarge = TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.w500,
+ );
+ static const subtitleMedium = TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w400,
+ );
+ static const subtitleSmall = TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.w300,
+ );
+ static const bodyVeryLarge = TextStyle();
+ static const bodyLarge = TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ );
+ static const bodyMedium = TextStyle(
+ fontSize: 14,
+ fontWeight: FontWeight.normal,
+ );
+ static const bodySmall = TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w300,
+ );
+ static const bodyVerySmall = TextStyle();
+}
diff --git a/apps/multichoice/lib/app/view/theme/theme_extension/app_theme_extension.dart b/apps/multichoice/lib/app/view/theme/theme_extension/app_theme_extension.dart
new file mode 100644
index 00000000..da3c676d
--- /dev/null
+++ b/apps/multichoice/lib/app/view/theme/theme_extension/app_theme_extension.dart
@@ -0,0 +1,12 @@
+import 'package:flutter/material.dart';
+import 'package:multichoice/app/view/theme/app_theme.dart';
+import 'package:theme/theme.dart';
+
+extension AppThemeExtension on ThemeData {
+ /// Usage example: Theme.of(context).appColors;
+ AppColorsExtension get appColors =>
+ extension() ?? AppTheme.lightAppColors;
+
+ AppTextExtension get appTextTheme =>
+ extension() ?? AppTheme.lightTextTheme;
+}
diff --git a/apps/multichoice/lib/bootstrap.dart b/apps/multichoice/lib/bootstrap.dart
index f5a85a9d..7d01f017 100644
--- a/apps/multichoice/lib/bootstrap.dart
+++ b/apps/multichoice/lib/bootstrap.dart
@@ -3,7 +3,9 @@ import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:core/core.dart';
+import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
+import 'package:multichoice/firebase_options.dart';
class SimpleBlocObserver extends BlocObserver {
const SimpleBlocObserver();
@@ -40,5 +42,8 @@ class SimpleBlocObserver extends BlocObserver {
Future bootstrap() async {
WidgetsFlutterBinding.ensureInitialized();
await configureCoreDependencies();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
Bloc.observer = const SimpleBlocObserver();
}
diff --git a/apps/multichoice/lib/constants/border_constants.dart b/apps/multichoice/lib/constants/border_constants.dart
index 8908580e..9830c7b6 100644
--- a/apps/multichoice/lib/constants/border_constants.dart
+++ b/apps/multichoice/lib/constants/border_constants.dart
@@ -1,5 +1,9 @@
import 'package:flutter/material.dart';
-final circularBorder5 = BorderRadius.circular(5);
+final borderCircular5 = BorderRadius.circular(5);
-final circularBorder12 = BorderRadius.circular(12);
+final borderCircular8 = BorderRadius.circular(8);
+
+final borderCircular12 = BorderRadius.circular(12);
+
+final borderCircular16 = BorderRadius.circular(16);
diff --git a/apps/multichoice/lib/constants/export_constants.dart b/apps/multichoice/lib/constants/export_constants.dart
index 50c75012..5632b19e 100644
--- a/apps/multichoice/lib/constants/export_constants.dart
+++ b/apps/multichoice/lib/constants/export_constants.dart
@@ -1,2 +1,3 @@
export 'border_constants.dart';
export 'spacing_constants.dart';
+export 'ui_constants.dart';
diff --git a/apps/multichoice/lib/constants/spacing_constants.dart b/apps/multichoice/lib/constants/spacing_constants.dart
index 49174dd8..2c729b53 100644
--- a/apps/multichoice/lib/constants/spacing_constants.dart
+++ b/apps/multichoice/lib/constants/spacing_constants.dart
@@ -4,6 +4,8 @@ import 'package:gap/gap.dart';
// Gaps
const gap4 = Gap(4);
+const gap8 = Gap(8);
+
const gap10 = Gap(10);
const gap12 = Gap(12);
@@ -14,7 +16,13 @@ const gap20 = Gap(20);
const gap24 = Gap(24);
+const gap56 = Gap(56);
+
// Padding
+const zeroPadding = EdgeInsets.zero;
+
+const allPadding2 = EdgeInsets.all(2);
+
const allPadding4 = EdgeInsets.all(4);
const allPadding6 = EdgeInsets.all(6);
@@ -29,6 +37,14 @@ const vertical8 = EdgeInsets.symmetric(vertical: 8);
const left4 = EdgeInsets.only(left: 4);
+const left4top2 = EdgeInsets.only(left: 4, top: 2);
+
+const left4top4 = EdgeInsets.only(left: 4, top: 4);
+
const right4 = EdgeInsets.only(right: 4);
const top12 = EdgeInsets.only(top: 12);
+
+const vertical8horizontal4 = EdgeInsets.symmetric(vertical: 8, horizontal: 4);
+
+const vertical12horizontal4 = EdgeInsets.symmetric(horizontal: 4, vertical: 12);
diff --git a/apps/multichoice/lib/constants/ui_constants.dart b/apps/multichoice/lib/constants/ui_constants.dart
new file mode 100644
index 00000000..ebff4cfe
--- /dev/null
+++ b/apps/multichoice/lib/constants/ui_constants.dart
@@ -0,0 +1,50 @@
+import 'package:flutter/material.dart';
+
+const outlinedButtonMinimumSize = Size(96, 48);
+const elevatedButtonMinimumSize = Size(96, 48);
+
+const entryCardMinimumSize = null;
+const entryCardMinimumHeight = 90.0;
+const entryCardMinimumWidth = 0;
+const tabCardMinimumWidth = 120.0;
+double tabsHeightConstant = 1.15;
+double tabsWidthConstant = 3.65;
+
+const _mobileScreenWidth = 450;
+// const _desktopScreenWidth = 1920;
+
+class UIConstants {
+ UIConstants();
+
+ static double? entryHeight(BuildContext context) {
+ final mediaHeight = MediaQuery.sizeOf(context).height / 12;
+
+ if (mediaHeight < entryCardMinimumHeight) {
+ return entryCardMinimumHeight;
+ }
+ return mediaHeight;
+ }
+
+ static double? tabHeight(BuildContext context) {
+ return MediaQuery.sizeOf(context).height / tabsHeightConstant;
+ }
+
+ static double? tabWidth(BuildContext context) {
+ final mediaWidth = MediaQuery.sizeOf(context).width;
+
+ if (mediaWidth > _mobileScreenWidth) {
+ tabsWidthConstant = 8;
+ }
+
+ final tabsWidth = mediaWidth / tabsWidthConstant;
+
+ if (tabsWidth < tabCardMinimumWidth) {
+ return tabCardMinimumWidth;
+ }
+ return tabsWidth;
+ }
+
+ static double? newTabWidth(BuildContext context) {
+ return MediaQuery.sizeOf(context).width / 6;
+ }
+}
diff --git a/apps/multichoice/lib/firebase_options.dart b/apps/multichoice/lib/firebase_options.dart
new file mode 100644
index 00000000..c38605c4
--- /dev/null
+++ b/apps/multichoice/lib/firebase_options.dart
@@ -0,0 +1,76 @@
+// File generated by FlutterFire CLI.
+// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
+import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
+import 'package:flutter/foundation.dart'
+ show defaultTargetPlatform, kIsWeb, TargetPlatform;
+import 'auth/secrets.dart';
+
+/// Default [FirebaseOptions] for use with your Firebase apps.
+///
+/// Example:
+/// ```dart
+/// import 'firebase_options.dart';
+/// // ...
+/// await Firebase.initializeApp(
+/// options: DefaultFirebaseOptions.currentPlatform,
+/// );
+/// ```
+class DefaultFirebaseOptions {
+ static FirebaseOptions get currentPlatform {
+ if (kIsWeb) {
+ return web;
+ }
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.android:
+ return android;
+ case TargetPlatform.iOS:
+ return ios;
+ case TargetPlatform.macOS:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for macos - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ case TargetPlatform.windows:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for windows - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ case TargetPlatform.linux:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions have not been configured for linux - '
+ 'you can reconfigure this by running the FlutterFire CLI again.',
+ );
+ default:
+ throw UnsupportedError(
+ 'DefaultFirebaseOptions are not supported for this platform.',
+ );
+ }
+ }
+
+ static FirebaseOptions web = FirebaseOptions(
+ apiKey: '${webApiKey}',
+ appId: '${webAppId}',
+ messagingSenderId: '82796040762',
+ projectId: 'multichoice-412309',
+ authDomain: 'multichoice-412309.firebaseapp.com',
+ storageBucket: 'multichoice-412309.appspot.com',
+ measurementId: 'G-RKRDGDJMDK',
+ );
+
+ static FirebaseOptions android = FirebaseOptions(
+ apiKey: '${androidApiKey}',
+ appId: '${androidAppId}',
+ messagingSenderId: '82796040762',
+ projectId: 'multichoice-412309',
+ storageBucket: 'multichoice-412309.appspot.com',
+ );
+
+ static FirebaseOptions ios = FirebaseOptions(
+ apiKey: '${iosApiKey}',
+ appId: '${iosAppId}',
+ messagingSenderId: '82796040762',
+ projectId: 'multichoice-412309',
+ storageBucket: 'multichoice-412309.appspot.com',
+ iosBundleId: 'com.example.multichoice',
+ );
+}
diff --git a/apps/multichoice/lib/presentation/edit/edit_entry_page.dart b/apps/multichoice/lib/presentation/edit/edit_entry_page.dart
index 916dda3b..cad0df5e 100644
--- a/apps/multichoice/lib/presentation/edit/edit_entry_page.dart
+++ b/apps/multichoice/lib/presentation/edit/edit_entry_page.dart
@@ -20,9 +20,9 @@ class EditEntryPage extends StatelessWidget {
child: BlocBuilder(
builder: (context, state) {
return Scaffold(
- backgroundColor: Colors.blue[100],
appBar: AppBar(
title: const Text('Edit entry'),
+ centerTitle: false,
leading: IconButton(
onPressed: () {
ctx.read().add(const HomeEvent.onPressedCancel());
@@ -57,7 +57,7 @@ class _EditEntryPage extends StatelessWidget {
}
return Padding(
- padding: allPadding12,
+ padding: allPadding18,
child: Form(
key: formKey,
child: Column(
@@ -90,7 +90,16 @@ class _EditEntryPage extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
- TextButton(
+ OutlinedButton(
+ onPressed: () {
+ context.read().add(
+ const HomeEvent.onPressedCancel(),
+ );
+ context.router.popUntilRoot();
+ },
+ child: const Text('Cancel'),
+ ),
+ ElevatedButton(
onPressed: state.isValid && state.entry.title.isNotEmpty
? () {
context
@@ -102,15 +111,6 @@ class _EditEntryPage extends StatelessWidget {
: null,
child: const Text('Ok'),
),
- TextButton(
- onPressed: () {
- context.read().add(
- const HomeEvent.onPressedCancel(),
- );
- context.router.popUntilRoot();
- },
- child: const Text('Cancel'),
- ),
],
),
],
diff --git a/apps/multichoice/lib/presentation/edit/edit_tab_page.dart b/apps/multichoice/lib/presentation/edit/edit_tab_page.dart
index 4243d756..9d059889 100644
--- a/apps/multichoice/lib/presentation/edit/edit_tab_page.dart
+++ b/apps/multichoice/lib/presentation/edit/edit_tab_page.dart
@@ -18,9 +18,9 @@ class EditTabPage extends StatelessWidget {
return BlocProvider.value(
value: ctx.read(),
child: Scaffold(
- backgroundColor: Colors.blue[100],
appBar: AppBar(
title: const Text('Edit Tab'),
+ centerTitle: false,
leading: IconButton(
onPressed: () {
ctx.read().add(const HomeEvent.onPressedCancel());
@@ -53,7 +53,7 @@ class _EditPage extends StatelessWidget {
}
return Padding(
- padding: allPadding12,
+ padding: allPadding18,
child: Form(
key: formKey,
child: Column(
@@ -86,7 +86,16 @@ class _EditPage extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
- TextButton(
+ OutlinedButton(
+ onPressed: () {
+ context.read().add(
+ const HomeEvent.onPressedCancel(),
+ );
+ context.router.popUntilRoot();
+ },
+ child: const Text('Cancel'),
+ ),
+ ElevatedButton(
onPressed: state.isValid && state.tab.title.isNotEmpty
? () {
context
@@ -98,15 +107,6 @@ class _EditPage extends StatelessWidget {
: null,
child: const Text('Ok'),
),
- TextButton(
- onPressed: () {
- context.read().add(
- const HomeEvent.onPressedCancel(),
- );
- context.router.popUntilRoot();
- },
- child: const Text('Cancel'),
- ),
],
),
],
diff --git a/apps/multichoice/lib/presentation/home/home_page.dart b/apps/multichoice/lib/presentation/home/home_page.dart
index 0b550be1..9c5c6d93 100644
--- a/apps/multichoice/lib/presentation/home/home_page.dart
+++ b/apps/multichoice/lib/presentation/home/home_page.dart
@@ -5,14 +5,18 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:models/models.dart';
import 'package:multichoice/app/engine/app_router.gr.dart';
-import 'package:multichoice/constants/border_constants.dart';
-import 'package:multichoice/constants/spacing_constants.dart';
+import 'package:multichoice/app/extensions/extension_getters.dart';
+import 'package:multichoice/app/view/theme/app_theme.dart';
+import 'package:multichoice/app/view/theme/theme_extension/app_theme_extension.dart';
+import 'package:multichoice/constants/export_constants.dart';
import 'package:multichoice/presentation/shared/widgets/add_widgets/_base.dart';
import 'package:multichoice/utils/custom_dialog.dart';
import 'package:multichoice/utils/custom_scroll_behaviour.dart';
+import 'package:shared_preferences/shared_preferences.dart';
part 'widgets/cards.dart';
part 'widgets/entry_card.dart';
+part 'widgets/drawer.dart';
part 'widgets/menu_widget.dart';
part 'widgets/new_entry.dart';
part 'widgets/new_tab.dart';
@@ -25,27 +29,50 @@ class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => coreSl()..add(const HomeEvent.onGetTabs()),
+ create: (context) => coreSl()
+ ..add(
+ const HomeEvent.onGetTabs(),
+ ),
child: BlocBuilder(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Multichoice'),
- centerTitle: true,
- backgroundColor: Colors.lightBlue,
actions: [
IconButton(
- onPressed: () {
- context
- .read()
- .add(const HomeEvent.onPressedDeleteAll());
- },
+ onPressed: state.tabs != null
+ ? () {
+ CustomDialog.show(
+ context: context,
+ title: const Text('Delete all tabs and entries?'),
+ content: const Text(
+ 'Are you sure you want to delete all tabs and their entries?',
+ ),
+ actions: [
+ OutlinedButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: const Text('No, cancel'),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ context.read().add(
+ const HomeEvent.onPressedDeleteAll(),
+ );
+ Navigator.of(context).pop();
+ },
+ child: const Text('Yes, delete'),
+ ),
+ ],
+ );
+ }
+ : null,
icon: const Icon(
Icons.delete_sweep_rounded,
),
),
],
),
+ drawer: const _HomeDrawer(),
body: const _HomePage(),
);
},
@@ -63,11 +90,17 @@ class _HomePage extends StatelessWidget {
builder: (context, state) {
final tabs = state.tabs ?? [];
+ if (state.isLoading) {
+ return const Center(
+ child: CircularProgressIndicator.adaptive(),
+ );
+ }
+
return Center(
child: Padding(
padding: allPadding12,
child: SizedBox(
- height: MediaQuery.sizeOf(context).height / 1.25,
+ height: UIConstants.tabHeight(context),
child: CustomScrollView(
scrollDirection: Axis.horizontal,
controller: ScrollController(),
diff --git a/apps/multichoice/lib/presentation/home/widgets/drawer.dart b/apps/multichoice/lib/presentation/home/widgets/drawer.dart
new file mode 100644
index 00000000..79ac8a36
--- /dev/null
+++ b/apps/multichoice/lib/presentation/home/widgets/drawer.dart
@@ -0,0 +1,122 @@
+part of '../home_page.dart';
+
+class _HomeDrawer extends StatelessWidget {
+ const _HomeDrawer();
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder(
+ builder: (context, state) {
+ final appVersion = coreSl().getAppVersion();
+ final sharedPref = coreSl();
+
+ return Drawer(
+ width: MediaQuery.sizeOf(context).width,
+ backgroundColor: context.theme.appColors.background,
+ child: Padding(
+ padding: allPadding12,
+ child: Column(
+ children: [
+ gap56,
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ 'Settings',
+ style: context.theme.appTextTheme.titleMedium?.copyWith(
+ color: Colors.white,
+ ),
+ ),
+ ),
+ IconButton(
+ onPressed: () {
+ if (Navigator.canPop(context)) {
+ Navigator.pop(context);
+ }
+ },
+ icon: const Icon(
+ Icons.close_outlined,
+ size: 28,
+ ),
+ ),
+ ],
+ ),
+ gap24,
+ Row(
+ children: [
+ const Expanded(
+ child: Text('Light/Dark Mode'),
+ ),
+ _ThemeButton(
+ sharedPref: sharedPref,
+ state: state,
+ ),
+ ],
+ ),
+ const Expanded(
+ child: SizedBox.expand(),
+ ),
+ Padding(
+ padding: allPadding12,
+ child: FutureBuilder(
+ future: appVersion,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done &&
+ snapshot.hasData) {
+ return Text(
+ 'V${snapshot.data}',
+ style: context.theme.appTextTheme.bodyMedium,
+ );
+ }
+ return const SizedBox.shrink();
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _ThemeButton extends StatelessWidget {
+ const _ThemeButton({
+ required this.sharedPref,
+ required this.state,
+ });
+
+ final SharedPreferences sharedPref;
+ final HomeState state;
+
+ @override
+ Widget build(BuildContext context) {
+ if (sharedPref.getString('theme') == 'light') {
+ return IconButton(
+ onPressed: () {
+ _darkMode(context);
+ sharedPref.setString('theme', 'dark');
+ },
+ icon: const Icon(Icons.dark_mode_outlined),
+ );
+ } else if (sharedPref.getString('theme') == 'dark') {
+ return IconButton(
+ onPressed: () {
+ _lightMode(context);
+ sharedPref.setString('theme', 'light');
+ },
+ icon: const Icon(Icons.light_mode_outlined),
+ );
+ }
+ return const SizedBox.shrink();
+ }
+
+ void _lightMode(BuildContext context) {
+ context.read().themeMode = ThemeMode.light;
+ }
+
+ void _darkMode(BuildContext context) {
+ context.read().themeMode = ThemeMode.dark;
+ }
+}
diff --git a/apps/multichoice/lib/presentation/home/widgets/entry_card.dart b/apps/multichoice/lib/presentation/home/widgets/entry_card.dart
index be19be05..f5e53758 100644
--- a/apps/multichoice/lib/presentation/home/widgets/entry_card.dart
+++ b/apps/multichoice/lib/presentation/home/widgets/entry_card.dart
@@ -20,7 +20,9 @@ class _EntryCard extends HookWidget {
menuChildren: [
MenuItemButton(
onPressed: () {
- context.read().add(HomeEvent.onUpdateEntry(entry.id));
+ context.read().add(
+ HomeEvent.onUpdateEntry(entry.id),
+ );
context.router.push(EditEntryPageRoute(ctx: context));
},
child: Text(MenuItems.edit.name),
@@ -78,6 +80,18 @@ class _EntryCard extends HookWidget {
),
],
child: GestureDetector(
+ onTap: () {
+ CustomDialog.show(
+ context: context,
+ title: SizedBox(
+ width: 150,
+ child: Text(
+ entry.title,
+ ),
+ ),
+ content: Text(entry.subtitle),
+ );
+ },
onDoubleTap: () {
context.read().add(HomeEvent.onUpdateEntry(entry.id));
context.router.push(EditEntryPageRoute(ctx: context));
@@ -89,31 +103,38 @@ class _EntryCard extends HookWidget {
menuController.open();
}
},
- child: Card(
- elevation: 7,
- shadowColor: Colors.white,
- shape: RoundedRectangleBorder(
- borderRadius: circularBorder5,
- ),
- color: const Color.fromARGB(255, 81, 153, 187),
- child: Padding(
- padding: allPadding4,
- child: Column(
- children: [
- Text(
- entry.title,
- style: const TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.w500,
- ),
- ),
- Text(
- entry.subtitle,
- style: const TextStyle(
- fontSize: 14,
- ),
+ child: Padding(
+ padding: allPadding4,
+ child: Card(
+ elevation: 3,
+ shadowColor: Colors.grey[400],
+ shape: RoundedRectangleBorder(
+ borderRadius: borderCircular5,
+ ),
+ margin: EdgeInsets.zero,
+ color: context.theme.appColors.secondary,
+ child: Padding(
+ padding: allPadding4,
+ child: SizedBox(
+ height: UIConstants.entryHeight(context),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ Text(
+ entry.title,
+ style: context.theme.appTextTheme.titleSmall,
+ overflow: TextOverflow.ellipsis,
+ maxLines: 1,
+ ),
+ Text(
+ entry.subtitle,
+ style: context.theme.appTextTheme.subtitleSmall,
+ overflow: TextOverflow.ellipsis,
+ maxLines: 2,
+ ),
+ ],
),
- ],
+ ),
),
),
),
diff --git a/apps/multichoice/lib/presentation/home/widgets/menu_widget.dart b/apps/multichoice/lib/presentation/home/widgets/menu_widget.dart
index 97100e7a..1f193feb 100644
--- a/apps/multichoice/lib/presentation/home/widgets/menu_widget.dart
+++ b/apps/multichoice/lib/presentation/home/widgets/menu_widget.dart
@@ -1,9 +1,8 @@
part of '../home_page.dart';
-class MenuWidget extends StatelessWidget {
- const MenuWidget({
+class _MenuWidget extends StatelessWidget {
+ const _MenuWidget({
required this.tab,
- super.key,
});
final TabsDTO tab;
@@ -23,8 +22,11 @@ class MenuWidget extends StatelessWidget {
menuController.open();
}
},
+ // visualDensity: VisualDensity.adaptivePlatformDensity,
icon: const Icon(Icons.more_vert_outlined),
- padding: EdgeInsets.zero,
+ iconSize: 20,
+ color: context.theme.appColors.ternary,
+ padding: zeroPadding,
);
},
menuChildren: [
@@ -36,51 +38,53 @@ class MenuWidget extends StatelessWidget {
child: Text(MenuItems.edit.name),
),
MenuItemButton(
- onPressed: () {
- CustomDialog.show(
- context: context,
- title: RichText(
- text: TextSpan(
- text: 'Delete all entries of ',
- style: DefaultTextStyle.of(context)
- .style
- .copyWith(fontSize: 24),
- children: [
- TextSpan(
- text: tab.title,
- style: const TextStyle(
- fontWeight: FontWeight.bold,
+ onPressed: tab.entries.isNotEmpty
+ ? () {
+ CustomDialog.show(
+ context: context,
+ title: RichText(
+ text: TextSpan(
+ text: 'Delete all entries of ',
+ style: DefaultTextStyle.of(context)
+ .style
+ .copyWith(fontSize: 24),
+ children: [
+ TextSpan(
+ text: tab.title,
+ style: const TextStyle(
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ TextSpan(
+ text: '?',
+ style: DefaultTextStyle.of(context)
+ .style
+ .copyWith(fontSize: 24),
+ ),
+ ],
),
),
- TextSpan(
- text: '?',
- style: DefaultTextStyle.of(context)
- .style
- .copyWith(fontSize: 24),
+ content: Text(
+ 'Are you sure you want to delete all the entries of ${tab.title}?',
),
- ],
- ),
- ),
- content: Text(
- 'Are you sure you want to delete all the entries of ${tab.title}?',
- ),
- actions: [
- OutlinedButton(
- onPressed: () => Navigator.of(context).pop(),
- child: const Text('Cancel'),
- ),
- ElevatedButton(
- onPressed: () {
- context.read().add(
- HomeEvent.onPressedDeleteAllEntries(tab.id),
- );
- Navigator.of(context).pop();
- },
- child: const Text('Delete Entries'),
- ),
- ],
- );
- },
+ actions: [
+ OutlinedButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: const Text('Cancel'),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ context.read().add(
+ HomeEvent.onPressedDeleteAllEntries(tab.id),
+ );
+ Navigator.of(context).pop();
+ },
+ child: const Text('Delete Entries'),
+ ),
+ ],
+ );
+ }
+ : null,
child: Text(MenuItems.deleteEntries.name),
),
MenuItemButton(
diff --git a/apps/multichoice/lib/presentation/home/widgets/new_entry.dart b/apps/multichoice/lib/presentation/home/widgets/new_entry.dart
index 3c1aa4bf..828f0d2d 100644
--- a/apps/multichoice/lib/presentation/home/widgets/new_entry.dart
+++ b/apps/multichoice/lib/presentation/home/widgets/new_entry.dart
@@ -16,15 +16,16 @@ class _NewEntry extends StatelessWidget {
builder: (context, state) {
final homeBloc = context.read();
return AddEntryCard(
- padding: allPadding6,
+ padding: zeroPadding,
onPressed: () {
CustomDialog.show(
context: context,
title: RichText(
text: TextSpan(
text: 'Add New Entry',
- style:
- DefaultTextStyle.of(context).style.copyWith(fontSize: 24),
+ style: DefaultTextStyle.of(context).style.copyWith(
+ fontSize: 24,
+ ),
),
),
content: BlocProvider.value(
@@ -47,6 +48,7 @@ class _NewEntry extends StatelessWidget {
hintText: 'Title',
),
),
+ gap10,
TextFormField(
controller: subtitleTextController,
onChanged: (value) => context.read().add(
diff --git a/apps/multichoice/lib/presentation/home/widgets/new_tab.dart b/apps/multichoice/lib/presentation/home/widgets/new_tab.dart
index e74708f0..1bc08320 100644
--- a/apps/multichoice/lib/presentation/home/widgets/new_tab.dart
+++ b/apps/multichoice/lib/presentation/home/widgets/new_tab.dart
@@ -12,7 +12,7 @@ class _NewTab extends StatelessWidget {
builder: (context, state) {
final homeBloc = context.read();
return AddTabCard(
- width: MediaQuery.sizeOf(context).width / 4,
+ width: UIConstants.newTabWidth(context),
onPressed: () {
CustomDialog.show(
context: context,
@@ -35,6 +35,7 @@ class _NewTab extends StatelessWidget {
hintText: 'Title',
),
),
+ gap10,
TextFormField(
controller: subtitleTextController,
onChanged: (value) => context
diff --git a/apps/multichoice/lib/presentation/home/widgets/vertical_tab.dart b/apps/multichoice/lib/presentation/home/widgets/vertical_tab.dart
index 1dafcd41..a1d2454a 100644
--- a/apps/multichoice/lib/presentation/home/widgets/vertical_tab.dart
+++ b/apps/multichoice/lib/presentation/home/widgets/vertical_tab.dart
@@ -18,7 +18,9 @@ class _VerticalTab extends StatelessWidget {
title: RichText(
text: TextSpan(
text: 'Delete ',
- style: DefaultTextStyle.of(context).style.copyWith(fontSize: 24),
+ style: DefaultTextStyle.of(context).style.copyWith(
+ fontSize: 24,
+ ),
children: [
TextSpan(
text: tab.title,
@@ -28,8 +30,9 @@ class _VerticalTab extends StatelessWidget {
),
TextSpan(
text: '?',
- style:
- DefaultTextStyle.of(context).style.copyWith(fontSize: 24),
+ style: DefaultTextStyle.of(context).style.copyWith(
+ fontSize: 24,
+ ),
),
],
),
@@ -54,54 +57,49 @@ class _VerticalTab extends StatelessWidget {
],
);
},
- child: Padding(
- padding: right4,
- child: Card(
- color: Colors.grey[200],
- elevation: 5,
- shadowColor: Colors.white,
- shape: RoundedRectangleBorder(
- borderRadius: circularBorder12,
- ),
- child: Padding(
- padding: allPadding4,
- child: SizedBox(
- width: MediaQuery.sizeOf(context).width / 4,
- child: Column(
- children: [
- Row(
- children: [
- Expanded(
- child: Padding(
- padding: left4,
- child: Text(
- tab.title,
- style: const TextStyle(
- fontSize: 20,
- fontWeight: FontWeight.w500,
- color: Colors.black,
- ),
- ),
+ child: Card(
+ color: context.theme.appColors.primary,
+ child: Padding(
+ padding: allPadding2,
+ child: SizedBox(
+ width: UIConstants.tabWidth(context),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Padding(
+ padding: left4,
+ child: Text(
+ tab.title,
+ style: context.theme.appTextTheme.titleMedium,
),
),
- MenuWidget(tab: tab),
- ],
- ),
- const Divider(
- indent: 4,
- endIndent: 4,
- ),
+ ),
+ _MenuWidget(tab: tab),
+ ],
+ ),
+ if (tab.subtitle.isEmpty)
+ const SizedBox.shrink()
+ else
Padding(
padding: left4,
child: Text(
tab.subtitle,
- style: const TextStyle(color: Colors.black),
+ style: context.theme.appTextTheme.subtitleMedium,
),
),
- gap10,
- _Cards(id: tab.id, entries: entries),
- ],
- ),
+ Divider(
+ color: context.theme.appColors.secondaryLight,
+ thickness: 2,
+ indent: 4,
+ endIndent: 4,
+ ),
+ gap4,
+ _Cards(id: tab.id, entries: entries),
+ ],
),
),
),
diff --git a/apps/multichoice/lib/presentation/shared/widgets/add_widgets/_base.dart b/apps/multichoice/lib/presentation/shared/widgets/add_widgets/_base.dart
index 5293550f..87d360a0 100644
--- a/apps/multichoice/lib/presentation/shared/widgets/add_widgets/_base.dart
+++ b/apps/multichoice/lib/presentation/shared/widgets/add_widgets/_base.dart
@@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
+import 'package:multichoice/app/extensions/extension_getters.dart';
+import 'package:multichoice/app/view/theme/theme_extension/app_theme_extension.dart';
import 'package:multichoice/constants/export_constants.dart';
part 'entry.dart';
diff --git a/apps/multichoice/lib/presentation/shared/widgets/add_widgets/entry.dart b/apps/multichoice/lib/presentation/shared/widgets/add_widgets/entry.dart
index aa8b67c0..209a7722 100644
--- a/apps/multichoice/lib/presentation/shared/widgets/add_widgets/entry.dart
+++ b/apps/multichoice/lib/presentation/shared/widgets/add_widgets/entry.dart
@@ -5,21 +5,23 @@ class AddEntryCard extends StatelessWidget {
required this.onPressed,
required this.padding,
this.semanticLabel,
+ this.color,
super.key,
});
final String? semanticLabel;
final VoidCallback onPressed;
final EdgeInsetsGeometry padding;
+ final Color? color;
@override
Widget build(BuildContext context) {
return _BaseCard(
semanticLabel: semanticLabel ?? '',
elevation: 5,
- color: Colors.grey[600],
+ color: color ?? context.theme.appColors.secondaryLight,
shape: RoundedRectangleBorder(
- borderRadius: circularBorder5,
+ borderRadius: borderCircular5,
),
padding: padding,
icon: const Icon(Icons.add_outlined),
diff --git a/apps/multichoice/lib/presentation/shared/widgets/add_widgets/tab.dart b/apps/multichoice/lib/presentation/shared/widgets/add_widgets/tab.dart
index 52c8002f..5ed99de2 100644
--- a/apps/multichoice/lib/presentation/shared/widgets/add_widgets/tab.dart
+++ b/apps/multichoice/lib/presentation/shared/widgets/add_widgets/tab.dart
@@ -5,21 +5,23 @@ class AddTabCard extends StatelessWidget {
required this.onPressed,
this.semanticLabel,
this.width,
+ this.color,
super.key,
});
final String? semanticLabel;
final double? width;
final VoidCallback onPressed;
+ final Color? color;
@override
Widget build(BuildContext context) {
return _BaseCard(
semanticLabel: semanticLabel ?? '',
elevation: 5,
- color: Colors.grey[600],
+ color: color ?? context.theme.appColors.primaryLight,
shape: RoundedRectangleBorder(
- borderRadius: circularBorder12,
+ borderRadius: borderCircular12,
),
child: Padding(
padding: allPadding6,
diff --git a/apps/multichoice/pubspec.yaml b/apps/multichoice/pubspec.yaml
index f1bb0f92..b4705c70 100644
--- a/apps/multichoice/pubspec.yaml
+++ b/apps/multichoice/pubspec.yaml
@@ -2,7 +2,7 @@ name: multichoice
description: "The application for the Multichoice repo"
publish_to: "none"
-version: 0.1.4+108
+version: 0.1.4+119
environment:
sdk: ">=3.3.0 <4.0.0"
@@ -11,12 +11,16 @@ dependencies:
auto_route: ^7.8.4
bloc: ^8.1.3
core: ^0.0.1
+ firebase_core: ^2.27.2
flutter:
sdk: flutter
flutter_bloc: ^8.1.4
flutter_hooks: ^0.20.5
gap: ^3.0.1
models: ^0.0.1
+ provider: ^6.1.1
+ shared_preferences: ^2.2.2
+ theme: ^0.0.1
window_size:
git:
url: https://github.com/google/flutter-desktop-embedding
diff --git a/apps/multichoice/pubspec_overrides.yaml b/apps/multichoice/pubspec_overrides.yaml
index dbebc64b..dcf73b58 100644
--- a/apps/multichoice/pubspec_overrides.yaml
+++ b/apps/multichoice/pubspec_overrides.yaml
@@ -1,6 +1,9 @@
+# melos_managed_dependency_overrides: theme
# melos_managed_dependency_overrides: core,models
dependency_overrides:
core:
path: ../../packages/core
models:
path: ../../packages/models
+ theme:
+ path: ../../packages/theme
diff --git a/config.yml b/config.yml
index 693297d2..626b26e7 100644
--- a/config.yml
+++ b/config.yml
@@ -7,6 +7,14 @@ build:
android_build_flag: true
windows_build_flag: false
+# This can be used to determine the environment to build the app in.
+# Note: This is used in both build_workflow and release_workflow
+# Options: profile, release (debug is by default empty)
+environment:
+ web_environment_flag: "release"
+ android_environment_flag: "release"
+ windows_environment_flag: "release"
+
# This can be used to determine the state of releases to Github.
release:
body: "Release notes here"
@@ -21,6 +29,7 @@ deploy:
android_deploy:
android_package_name: "co.za.zanderkotze.multichoice"
+ android_environment_url: "https://play.google.com/console/u/0/developers/8783535225973670504/app/4976133683768209199/tracks/internal-testing"
android_track: "alpha"
android_release_name: "v1.0.0"
android_deploy_status: "draft"
diff --git a/docs/8-investigate-theming-of-app-as-well-as-colours-1.md b/docs/8-investigate-theming-of-app-as-well-as-colours-1.md
new file mode 100644
index 00000000..eef227e2
--- /dev/null
+++ b/docs/8-investigate-theming-of-app-as-well-as-colours-1.md
@@ -0,0 +1,48 @@
+# [Investigate theming of app as well as colours](https://github.com/ZanderCowboy/multichoice/issues/8)
+
+## Ticket: [8](https://github.com/ZanderCowboy/multichoice/issues/8)
+
+### branch: `8-investigate-theming-of-app-as-well-as-colours-1`
+
+### Overview
+
+This ticket is primarily responsible for setting up theming in our app. I also implemented Light mode and Dark mode.
+
+### What was done
+
+- [X] Created a `Makefile` to avoid typing the commands in the terminal manually
+```make
+fb:
+ flutter pub get && dart run build_runner build --delete-conflicting-outputs
+
+db:
+ dart run build_runner build --delete-conflicting-outputs
+
+frb:
+ flutter clean && flutter pub get && dart run build_runner build --delete-conflicting-outputs
+```
+- [X] Restructured new code
+- [X] Created an app palette in `app_palette.dart`
+- [X] Created typography for the app in `app_typography.dart`
+- [X] Updated the UI with the new colors and text styles
+- [X] Created a simple `Drawer` on the HomePage
+- [X] Implemented `Light` mode and `Dark` mode
+- [X] Created `app_colors_extension` and `app_text_extension`, along with `AppThemeExtension`
+- [X] Created and updated main `AppTheme` in `app_theme.dart`
+- [X] Created a `ThemeGetter` to enable us to use `context.theme` with `ThemeData get theme => Theme.of(this);`
+- [X] Created `theme` package and added files
+
+### What needs to be done
+
+- [ ] Verify that our method of refreshing the theme button in the HomeBloc
+- [ ] Remove `theme_service` and interface `i_theme_service`
+
+### Resources
+
+- [name](https://medium.com/@alexandersnotes/flutter-custom-theme-with-themeextension-792034106abc)
+- [Don’t write Theme.of(context) ANYMORE](https://medium.com/@alexandersnotes/how-to-improve-flutter-code-with-extension-methods-99854a29692c)
+- [`theme_tailor`](https://pub.dev/packages/theme_tailor)
+- [`provider`](https://pub.dev/packages/provider)
+- [`shared_preferences`](https://pub.dev/packages/shared_preferences)
+- [Mobile Palette Generator](https://mobilepalette.colorion.co/)
+- [colormagic](https://colormagic.app/)
diff --git a/docs/94-add-firebase-app-distribution-to-the-app.md b/docs/94-add-firebase-app-distribution-to-the-app.md
new file mode 100644
index 00000000..6036086a
--- /dev/null
+++ b/docs/94-add-firebase-app-distribution-to-the-app.md
@@ -0,0 +1,30 @@
+# [Add Firebase App Distribution to the App](https://github.com/ZanderCowboy/multichoice/issues/94)
+
+## Ticket: [94](https://github.com/ZanderCowboy/multichoice/issues/94)
+
+### branch: `94-add-firebase-app-distribution-to-the-app`
+
+### Overview
+
+This ticket is about setting up `Firebase App Distribution` to our app
+
+### What was done
+
+- [X] Add `google-services.json` file with config
+- [X] Update `build.gradle` files for Google Services
+- [X] Set up firebase config
+- [X] Update `_build-android-app.yml` file for firebase app distribution
+- [X] Update workflows overall
+- [X] Fix firebase_options.dart not found by GitHub Actions
+
+### What needs to be done
+
+### Resources
+
+- [Firebase CLI reference](https://firebase.google.com/docs/cli#install-cli-windows)
+- [Add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup?platform=android)
+- [Service accounts for project 'Multichoice'](https://console.cloud.google.com/iam-admin/serviceaccounts?project=multichoice-412309)
+- [Github Action and Firebase App Distribution — CI/CD Ways — Part 2](https://steveos.medium.com/github-action-and-firebase-app-distribution-ci-cd-ways-part-2-fcf9ba425c0)
+- [Firebase-Distribution-Github-Action](https://github.com/wzieba/Firebase-Distribution-Github-Action)
+
+- [](https://play.google.com/console/u/0/developers/8783535225973670504/app/4976133683768209199/tracks/internal-testing)
diff --git a/packages/core/analysis_options.yaml b/packages/core/analysis_options.yaml
index 6fa6881c..273898fa 100644
--- a/packages/core/analysis_options.yaml
+++ b/packages/core/analysis_options.yaml
@@ -11,3 +11,5 @@ analyzer:
- lib/generated/**
- lib/**.g.dart
- lib/**.freezed.dart
+ errors:
+ invalid_annotation_target: ignore
diff --git a/packages/core/lib/src/application/home/home_bloc.dart b/packages/core/lib/src/application/home/home_bloc.dart
index f40103c7..93fc38b3 100644
--- a/packages/core/lib/src/application/home/home_bloc.dart
+++ b/packages/core/lib/src/application/home/home_bloc.dart
@@ -34,14 +34,14 @@ class HomeBloc extends Bloc {
);
},
onGetTab: (value) async {
- emit(state.copyWith(isLoading: true));
+ // emit(state.copyWith(isLoading: true));
final tab = await _tabsRepository.getTab(value.tabId);
emit(
state.copyWith(
tab: tab,
- isLoading: false,
+ // isLoading: false,
),
);
},
@@ -171,7 +171,7 @@ class HomeBloc extends Bloc {
emit(
state.copyWith(
tab: TabsDTO.empty(),
- tabs: [],
+ tabs: null,
entry: EntryDTO.empty(),
entryCards: null,
isLoading: false,
@@ -283,12 +283,26 @@ class HomeBloc extends Bloc {
);
},
onUpdateTabId: (value) async {
+ emit(state.copyWith(isLoading: true));
+
final tab = await _tabsRepository.getTab(value.id);
- emit(state.copyWith(tab: tab, isValid: false));
+
+ emit(state.copyWith(
+ tab: tab,
+ isValid: false,
+ isLoading: false,
+ ));
},
onUpdateEntry: (value) async {
+ emit(state.copyWith(isLoading: true));
+
final entry = await _entryRepository.getEntry(value.id);
- emit(state.copyWith(entry: entry, isValid: false));
+
+ emit(state.copyWith(
+ entry: entry,
+ isValid: false,
+ isLoading: false,
+ ));
},
);
});
diff --git a/packages/core/lib/src/injectable_module.dart b/packages/core/lib/src/injectable_module.dart
index fed5e8c8..c2f235af 100644
--- a/packages/core/lib/src/injectable_module.dart
+++ b/packages/core/lib/src/injectable_module.dart
@@ -3,6 +3,7 @@ import 'package:isar/isar.dart';
import 'package:models/models.dart';
import 'package:path_provider/path_provider.dart';
+import 'package:shared_preferences/shared_preferences.dart';
@module
abstract class InjectableModule {
@@ -20,4 +21,13 @@ abstract class InjectableModule {
return isar;
}
+
+ @preResolve
+ Future get sharedPref async {
+ final sharedPref = await SharedPreferences.getInstance();
+
+ await sharedPref.setString('theme', 'light');
+
+ return sharedPref;
+ }
}
diff --git a/packages/core/lib/src/repositories/implementation/tabs/tabs_repository.dart b/packages/core/lib/src/repositories/implementation/tabs/tabs_repository.dart
index 4edae2a3..0d5cd945 100644
--- a/packages/core/lib/src/repositories/implementation/tabs/tabs_repository.dart
+++ b/packages/core/lib/src/repositories/implementation/tabs/tabs_repository.dart
@@ -150,3 +150,40 @@ class TabsRepository implements ITabsRepository {
}
}
}
+
+ // List updateTabs(int oldIndex, int newIndex) {
+ // final List tempTabs = tabsData.keys.toList();
+ // final int tempLength = tempTabs.length;
+ // final List> tempListEntry = tabsData.values.toList();
+
+ // final Tabs movedTab = tempTabs.removeAt(oldIndex);
+ // final List movedListEntry = tempListEntry.removeAt(oldIndex);
+
+ // if (oldIndex < newIndex && newIndex != tempLength) {
+ // if (newIndex - oldIndex == 2) {
+ // tempTabs.insert(newIndex - 1, movedTab);
+ // tempListEntry.insert(newIndex - 1, movedListEntry);
+ // } else {
+ // tempTabs.insert(newIndex - 1, movedTab);
+ // tempListEntry.insert(newIndex - 1, movedListEntry);
+ // }
+ // } else if (oldIndex > newIndex) {
+ // tempTabs.insert(newIndex, movedTab);
+ // tempListEntry.insert(newIndex, movedListEntry);
+ // } else {
+ // tempTabs.add(movedTab);
+ // tempListEntry.add(movedListEntry);
+ // }
+
+ // tabsData.clear();
+ // if (tempTabs.length == tempListEntry.length) {
+ // for (var i = 0; i < tempTabs.length; i++) {
+ // final tab = tempTabs[i];
+ // final listEntry = tempListEntry[i];
+
+ // tabsData[tab] = listEntry;
+ // }
+ // }
+
+ // return tabsData.keys.toList();
+ // }
\ No newline at end of file
diff --git a/packages/core/lib/src/services/implementations/login_service.dart b/packages/core/lib/src/services/implementations/login_service.dart
new file mode 100644
index 00000000..d1990916
--- /dev/null
+++ b/packages/core/lib/src/services/implementations/login_service.dart
@@ -0,0 +1,36 @@
+import 'package:core/src/services/interfaces/i_login_service.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class SessionImpl extends Session {
+ SessionImpl(this.sharedPref);
+
+ SharedPreferences sharedPref;
+
+ @override
+ void storeLoginInfo(String accessToken) {
+ sharedPref
+ ..setBool('login_status', true)
+ ..setString('access_token', accessToken);
+ }
+
+ @override
+ bool isUserLoggedIn() {
+ final isLoggedIn = sharedPref.getBool('login_status') ?? false;
+ return isLoggedIn;
+ }
+
+ @override
+ String getAccessToken() {
+ return sharedPref.getString('access_token') ?? '';
+ }
+
+ @override
+ void deleteLoginInfo() {
+ if (sharedPref.containsKey('login_status')) {
+ sharedPref.remove('login_status');
+ }
+ if (sharedPref.containsKey('access_token')) {
+ sharedPref.remove('access_token');
+ }
+ }
+}
diff --git a/packages/core/lib/src/services/interfaces/i_login_service.dart b/packages/core/lib/src/services/interfaces/i_login_service.dart
new file mode 100644
index 00000000..4fd3d3d2
--- /dev/null
+++ b/packages/core/lib/src/services/interfaces/i_login_service.dart
@@ -0,0 +1,6 @@
+abstract class Session {
+ void storeLoginInfo(String accessToken);
+ bool isUserLoggedIn();
+ String getAccessToken();
+ void deleteLoginInfo();
+}
diff --git a/packages/core/pubspec.yaml b/packages/core/pubspec.yaml
index 3d6c5ef5..45016750 100644
--- a/packages/core/pubspec.yaml
+++ b/packages/core/pubspec.yaml
@@ -20,6 +20,7 @@ dependencies:
models: ^0.0.1
package_info_plus: ^4.2.0
path_provider: ^2.1.2
+ shared_preferences: ^2.2.2
uuid: ^4.3.3
version: ^3.0.2
diff --git a/packages/theme/.gitignore b/packages/theme/.gitignore
new file mode 100644
index 00000000..9ada86e1
--- /dev/null
+++ b/packages/theme/.gitignore
@@ -0,0 +1,26 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# Generated Code
+**/*.tailor.dart
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Flutter/Dart/Pub related
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+build/
diff --git a/packages/theme/.metadata b/packages/theme/.metadata
new file mode 100644
index 00000000..48326e7c
--- /dev/null
+++ b/packages/theme/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "bae5e49bc2a867403c43b2aae2de8f8c33b037e4"
+ channel: "stable"
+
+project_type: package
diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md
new file mode 100644
index 00000000..03425463
--- /dev/null
+++ b/packages/theme/CHANGELOG.md
@@ -0,0 +1,7 @@
+# CHANGELOG
+
+This is the changelog for the `theme` package used in the Multichoice repo
+
+## 0.0.1
+
+* Initial set up of `theme` package
diff --git a/packages/theme/README.md b/packages/theme/README.md
new file mode 100644
index 00000000..ebcc9f2c
--- /dev/null
+++ b/packages/theme/README.md
@@ -0,0 +1,24 @@
+
+# Multichoice Theming
+
+This package contains all the theming used by the `multichoice` app.
+
+## Getting started and Installation
+
+TODO: List prerequisites and provide or point to information on how to
+start using the package.
+
+## Usage
+
+TODO: Include short and useful examples for package users. Add longer examples
+to `/example` folder.
+
+```dart
+const like = 'sample';
+```
+
+## Additional information
+
+TODO: Tell users more about the package: where to find more information, how to
+contribute to the package, how to file issues, what response they can expect
+from the package authors, and more.
diff --git a/packages/theme/analysis_options.yaml b/packages/theme/analysis_options.yaml
new file mode 100644
index 00000000..b5cd8979
--- /dev/null
+++ b/packages/theme/analysis_options.yaml
@@ -0,0 +1,5 @@
+include: package:very_good_analysis/analysis_options.yaml
+
+linter:
+ rules:
+ public_member_api_docs: false
diff --git a/packages/theme/lib/src/colors/app_colors_extension.dart b/packages/theme/lib/src/colors/app_colors_extension.dart
new file mode 100644
index 00000000..366d8a7f
--- /dev/null
+++ b/packages/theme/lib/src/colors/app_colors_extension.dart
@@ -0,0 +1,38 @@
+import 'package:flutter/material.dart';
+import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';
+
+part 'app_colors_extension.tailor.dart';
+
+@TailorMixin()
+class AppColorsExtension extends ThemeExtension
+ with _$AppColorsExtensionTailorMixin {
+ AppColorsExtension({
+ required this.primary,
+ required this.primaryLight,
+ required this.secondary,
+ required this.secondaryLight,
+ required this.ternary,
+ required this.foreground,
+ required this.background,
+ required this.white,
+ required this.black,
+ });
+ @override
+ final Color? primary;
+ @override
+ final Color? primaryLight;
+ @override
+ final Color? secondary;
+ @override
+ final Color? secondaryLight;
+ @override
+ final Color? ternary;
+ @override
+ final Color? foreground;
+ @override
+ final Color? background;
+ @override
+ final Color? white;
+ @override
+ final Color? black;
+}
diff --git a/packages/theme/lib/src/text/app_text_extension.dart b/packages/theme/lib/src/text/app_text_extension.dart
new file mode 100644
index 00000000..d9b24d4e
--- /dev/null
+++ b/packages/theme/lib/src/text/app_text_extension.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+import 'package:theme_tailor_annotation/theme_tailor_annotation.dart';
+
+part 'app_text_extension.tailor.dart';
+
+@TailorMixin()
+class AppTextExtension extends ThemeExtension
+ with _$AppTextExtensionTailorMixin {
+ AppTextExtension({
+ required this.body1,
+ required this.body2,
+ required this.h1,
+ required this.titleLarge,
+ required this.titleMedium,
+ required this.titleSmall,
+ required this.subtitleLarge,
+ required this.subtitleMedium,
+ required this.subtitleSmall,
+ required this.bodyLarge,
+ required this.bodyMedium,
+ required this.bodySmall,
+ });
+
+ @override
+ final TextStyle? h1;
+ @override
+ final TextStyle? body1;
+ @override
+ final TextStyle? body2;
+ @override
+ final TextStyle? titleLarge;
+ @override
+ final TextStyle? titleMedium;
+ @override
+ final TextStyle? titleSmall;
+ @override
+ final TextStyle? subtitleLarge;
+ @override
+ final TextStyle? subtitleMedium;
+ @override
+ final TextStyle? subtitleSmall;
+ @override
+ final TextStyle? bodyLarge;
+ @override
+ final TextStyle? bodyMedium;
+ @override
+ final TextStyle? bodySmall;
+}
diff --git a/packages/theme/lib/theme.dart b/packages/theme/lib/theme.dart
new file mode 100644
index 00000000..0193c670
--- /dev/null
+++ b/packages/theme/lib/theme.dart
@@ -0,0 +1,5 @@
+/// Multichoice Theme
+library theme;
+
+export 'src/colors/app_colors_extension.dart';
+export 'src/text/app_text_extension.dart';
diff --git a/packages/theme/pubspec.yaml b/packages/theme/pubspec.yaml
new file mode 100644
index 00000000..54a96272
--- /dev/null
+++ b/packages/theme/pubspec.yaml
@@ -0,0 +1,22 @@
+name: theme
+description: "The theme package used in the Multichoice repo."
+version: 0.0.1
+publish_to: "none"
+
+environment:
+ sdk: ">=3.3.0 <4.0.0"
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ theme_tailor_annotation: ^2.1.0
+
+dev_dependencies:
+ build_runner: ^2.4.8
+ flutter_test:
+ sdk: flutter
+ theme_tailor: ^2.1.0
+ very_good_analysis: ^5.1.0
+
+flutter:
diff --git a/packages/theme/test/theme_test.dart b/packages/theme/test/theme_test.dart
new file mode 100644
index 00000000..ab73b3a2
--- /dev/null
+++ b/packages/theme/test/theme_test.dart
@@ -0,0 +1 @@
+void main() {}