Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 216 additions & 0 deletions .github/workflows/deploy-to-appstore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
name: Deploy to App Store

# Copied from deploy-to-playstore.yaml
on:
workflow_dispatch:
inputs:
lane:
description: "Fastlane lane to use (internal, beta, promote_to_production)"
required: true
default: "internal"
tag:
description: "GitHub tag"
required: true
default: "latest"
ref:
description: "GitHub ref, if not provided, will use the tag"
required: false
default: ""
workflow_run:
workflows:
- Add Artifacts for Release
- Add Artifacts for Release Candidate
types: [completed]
release:
types: [published]

jobs:
# Extract some useful variable
# 1. lane - Same as 'workflow_dispatch' inputs, auto generate from tag name
# 2. dev_build_number - extract number of RC
# 3. flavor - 'dev'(internal) or 'prod'(beta)
# 4. build_code - pubspec.yaml build code.
var:
name: Extracting variables
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
outputs:
ref: ${{ github.event.inputs.ref || steps.tag.outputs.result }}
lane: ${{ steps.lane.outputs.result }}
dev_build_number: ${{ steps.dev_build_number.outputs.result }}
flavor: ${{ steps.flavor.outputs.value }}
build_code: ${{ steps.build_code.outputs.value }}
steps:
- name: Get latest tag
id: tag
uses: actions/github-script@v7
with:
result-encoding: string
script: |
if (context.eventName === 'release') {
return context.payload.release.tag_name;
}

if (context.eventName === 'workflow_dispatch') {
if (context.payload.inputs.tag !== 'latest') {
return context.payload.inputs.tag;
}
}

const res = await github.rest.repos.listTags({
owner: 'evan361425',
repo: 'flutter-pos-system',
per_page: 1,
});

return res.data[0].name;

- name: Extract lane
id: lane
uses: actions/github-script@v7
with:
result-encoding: string
script: |
return context.eventName === 'workflow_dispatch'
? '${{ github.event.inputs.lane }}'
: context.eventName === 'release'
? 'promote_to_production'
: '${{ steps.tag.outputs.result }}'.includes('-rc')
? 'internal'
: 'beta';

- name: Extract Flavor
id: flavor
uses: haya14busa/action-cond@v1
with:
cond: ${{ steps.lane.outputs.result == 'internal' }}
if_true: dev
if_false: prod

- name: Checkout code
uses: actions/checkout@v4
with:
ref: "refs/tags/${{ steps.tag.outputs.result }}"

- name: Extract build code
id: build_code
run: |
ver=$(grep -m 1 '^version: ' pubspec.yaml | cut -d' ' -f2)
echo "value=$(echo "$ver" | cut -f2- -d"+")" >> $GITHUB_OUTPUT

- name: Extract build number
id: dev_build_number
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const ref = '${{ steps.tag.outputs.result }}';
return ref.includes('-rc')
? ref.substr(ref.indexOf('-rc') + 3)
: ''.concat(${{ steps.build_code.outputs.value }} % 100);

fastlane-deploy:
runs-on: macos-latest
needs: var
steps:
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.35.x"
cache: true
channel: "stable"

- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ needs.var.outputs.ref }}

# Setup Ruby, Bundler, and Gemfile dependencies
- name: Setup Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
bundler-cache: true
working-directory: ios

- run: bundle exec fastlane --version
working-directory: ios

# Get flutter dependencies.
- name: Build dependencies
env:
GH_READ_PAT: ${{ secrets.GH_READ_PAT }}
run: |
flutter clean
echo "https://oauth:$GH_READ_PAT@github.com" > ~/.git-credentials
git config --global credential.helper store
flutter pub get

# Configure App Store Connect API Key
- name: Configure App Store Connect API Key
run: echo "$APP_STORE_CONNECT_API_KEY" > fastlane-api-key.json
env:
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
working-directory: ios/fastlane

# Configure certificates and provisioning profiles
- name: Configure code signing
run: |
# Import certificates
echo "$IOS_CERTIFICATE_P12" | base64 --decode > certificate.p12

# Create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain

# Import certificate to keychain
security import certificate.p12 -k build.keychain -P "$IOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain

# Install provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo "$IOS_PROVISION_PROFILE" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision
env:
IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }}
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
IOS_PROVISION_PROFILE: ${{ secrets.IOS_PROVISION_PROFILE }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
working-directory: ios

# Create ExportOptions.plist
- name: Create ExportOptions.plist
run: |
cat > ExportOptions.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>teamID</key>
<string>$TEAM_ID</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
EOF
env:
TEAM_ID: ${{ secrets.TEAM_ID }}
working-directory: ios

# Build and deploy with Fastlane (by default, to internal track) 🚀.
# Naturally, promote_to_production only deploys.
- name: Fastlane building
run: |
bundle exec fastlane ${{ needs.var.outputs.lane }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
ITC_TEAM_ID: ${{ secrets.ITC_TEAM_ID }}
TEAM_ID: ${{ secrets.TEAM_ID }}
BUILD_NUMBER: ${{ needs.var.outputs.dev_build_number }}
VERSION_CODE: ${{ needs.var.outputs.build_code }}
working-directory: ios
3 changes: 3 additions & 0 deletions .github/workflows/deploy-to-playstore.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Deploy to Play Store

# Any changes here should be mirrored to deploy-to-appstore.yaml
on:
workflow_dispatch:
# Enable manual run
Expand Down Expand Up @@ -142,6 +143,8 @@ jobs:
# Get flutter dependencies.
- name: Build dependencies
env:
# private package:
# - flutter-pos-packages
GH_READ_PAT: ${{ secrets.GH_READ_PAT }}
run: |
flutter clean
Expand Down
26 changes: 0 additions & 26 deletions .github/workflows/semgrep.yml

This file was deleted.

2 changes: 2 additions & 0 deletions ios/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ Runner/GeneratedPluginRegistrant.*
!default.perspectivev3
GoogleService-Info.plist
Podfile.lock
fastlane/fastlane-api-key.json
build.keychain
3 changes: 3 additions & 0 deletions ios/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source "https://rubygems.org"

gem "fastlane", ">= 2.220.0"
8 changes: 8 additions & 0 deletions ios/fastlane/Appfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
app_identifier("com.evanlu.possystem") # The bundle identifier of your app
apple_id(ENV["APPLE_ID"]) # Your Apple email address

itc_team_id(ENV["ITC_TEAM_ID"]) # App Store Connect Team ID
team_id(ENV["TEAM_ID"]) # Developer Portal Team ID

# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
50 changes: 35 additions & 15 deletions ios/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,43 @@ platform :ios do
sh "flutter build ios --no-codesign --dart-define=appFlavor=prod --dart-define=logLevel=info"
end

desc "Submit to TestFlight"
lane :beta do
# Build the app
sh "flutter build ios --dart-define=appFlavor=prod --dart-define=logLevel=info"
desc "Submit a new Internal Build to TestFlight (internal testing)"
lane :internal do
# Build the app for development
sh "flutter build ipa --export-options-plist=ExportOptions.plist --dart-define=appFlavor=dev --dart-define=logLevel=info"

# Code signing and upload would go here when certificates are available
# build_app(workspace: "Runner.xcworkspace", scheme: "Runner")
# upload_to_testflight
# Upload to TestFlight for internal testing
upload_to_testflight(
skip_waiting_for_build_processing: true,
ipa: "../../build/ios/ipa/*.ipa",
api_key_path: "fastlane-api-key.json",
)
end

desc "Submit to App Store"
lane :release do
# Build the app
sh "flutter build ios --dart-define=appFlavor=prod --dart-define=logLevel=info"
desc "Submit a new Beta Build to TestFlight (external testing)"
lane :beta do
# Build the app for production
sh "flutter build ipa --export-options-plist=ExportOptions.plist --dart-define=appFlavor=prod --dart-define=logLevel=info"

# Code signing and upload would go here when certificates are available
# build_app(workspace: "Runner.xcworkspace", scheme: "Runner")
# upload_to_app_store
# Upload to TestFlight for external testing
upload_to_testflight(
skip_waiting_for_build_processing: false,
ipa: "../../build/ios/ipa/*.ipa",
api_key_path: "fastlane-api-key.json",
)
end

desc "Promote beta to production (App Store)"
lane :promote_to_production do
# Deliver the latest build to App Store
deliver(
skip_metadata: false,
skip_screenshots: false,
skip_binary_upload: true,
submit_for_review: false,
automatic_release: false,
api_key_path: "fastlane-api-key.json",
build_number: ENV['VERSION_CODE'],
)
end
end
end
6 changes: 3 additions & 3 deletions lib/components/dialog/confirm_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ConfirmDialog extends StatelessWidget {
String? content,
Widget? body,
}) async {
final result = await showAdaptiveDialog<bool?>(
final result = await showDialog<bool?>(
context: context,
barrierDismissible: true,
builder: (_) => ConfirmDialog(
Expand All @@ -32,15 +32,15 @@ class ConfirmDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final local = MaterialLocalizations.of(context);
return AlertDialog.adaptive(
return AlertDialog(
title: Text(title),
content: content == null ? null : SingleChildScrollView(child: content),
actions: <Widget>[
PopButton(
key: const Key('confirm_dialog.cancel'),
title: local.cancelButtonLabel,
),
TextButton(
FilledButton(
key: const Key('confirm_dialog.confirm'),
onPressed: () => Navigator.of(context).pop(true),
child: Text(local.okButtonLabel),
Expand Down
8 changes: 4 additions & 4 deletions lib/components/dialog/delete_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ class DeleteDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final local = MaterialLocalizations.of(context);
return AlertDialog.adaptive(
return AlertDialog(
title: Text(S.dialogDeletionTitle),
content: SingleChildScrollView(
child: Text(content, textAlign: TextAlign.start),
),
actions: <Widget>[
PopButton(title: local.cancelButtonLabel),
TextButton(
FilledButton(
key: const Key('delete_dialog.confirm'),
onPressed: () => Navigator.of(context).pop(true),
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
style: FilledButton.styleFrom(backgroundColor: Colors.redAccent),
child: Text(local.deleteButtonTooltip),
),
],
Expand Down Expand Up @@ -59,7 +59,7 @@ class DeleteDialog extends StatelessWidget {
return startDelete();
}

final isConfirmed = await showAdaptiveDialog<bool>(
final isConfirmed = await showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) => DeleteDialog(content: content),
Expand Down