Skip to content
Open
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
28 changes: 22 additions & 6 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
outputs:
proceed: ${{ steps.check.outputs.proceed }}
is_prerelease: ${{ steps.check.outputs.is_prerelease }}
sentry_environment: ${{ steps.check.outputs.sentry_environment }}
steps:
- name: Inspect tag
id: check
Expand All @@ -48,19 +49,28 @@ jobs:
# Strict shape: vX.Y.Z with all parts numeric, no suffix.
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::notice::Tag $TAG is not a plain vX.Y.Z tag; skipping release."
echo "proceed=false" >> $GITHUB_OUTPUT
echo "is_prerelease=false" >> $GITHUB_OUTPUT
{
echo "proceed=false"
echo "is_prerelease=false"
echo "sentry_environment=internal"
} >> "$GITHUB_OUTPUT"
exit 0
fi
PATCH=$(echo "$TAG" | cut -d. -f3)
if [ "$PATCH" -eq 0 ]; then
echo "::notice::Tag $TAG has PATCH=0; running production-candidate release."
echo "proceed=true" >> $GITHUB_OUTPUT
echo "is_prerelease=false" >> $GITHUB_OUTPUT
{
echo "proceed=true"
echo "is_prerelease=false"
echo "sentry_environment=production"
} >> "$GITHUB_OUTPUT"
else
echo "::notice::Tag $TAG has PATCH=$PATCH; running internal release."
echo "proceed=true" >> $GITHUB_OUTPUT
echo "is_prerelease=true" >> $GITHUB_OUTPUT
{
echo "proceed=true"
echo "is_prerelease=true"
echo "sentry_environment=internal"
} >> "$GITHUB_OUTPUT"
fi

store-metadata-preflight:
Expand Down Expand Up @@ -172,6 +182,9 @@ jobs:
working-directory: android
env:
NEW_VERSION: ${{ github.ref_name }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_ENVIRONMENT: ${{ needs.guard.outputs.sentry_environment }}

- name: Upload APK artifact
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -269,6 +282,9 @@ jobs:
working-directory: ios
env:
NEW_VERSION: ${{ github.ref_name }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_ENVIRONMENT: ${{ needs.guard.outputs.sentry_environment }}

- name: Upload IPA artifact
uses: actions/upload-artifact@v4
Expand Down
104 changes: 83 additions & 21 deletions android/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,51 @@
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

require "json"
require "tempfile"

default_platform(:android)

# Keeps the DSN out of the process arguments Fastlane prints. Flutter reads
# both compile-time values from this short-lived, mode-0600 JSON file.
def with_crash_reporting_defines
dsn = ENV.fetch("SENTRY_DSN", "")
UI.user_error!("SENTRY_DSN is required for store release builds") if dsn.empty?

environment = ENV.fetch("SENTRY_ENVIRONMENT", "production")
file = Tempfile.new(["sentry-defines", ".json"])
File.chmod(0o600, file.path)
file.write(JSON.generate({
"SENTRY_DSN" => dsn,
"SENTRY_ENVIRONMENT" => environment,
}))
file.flush
yield file.path
ensure
file&.close!
end

def upload_sentry_symbols(marketing_version:, version_code:, symbols_path:, dart_symbol_map_path:)
ENV["SENTRY_RELEASE"] = "swiss.realunit.app@#{marketing_version}+#{version_code}"
ENV["SENTRY_DIST"] = version_code.to_s
Dir.chdir("../..") do
# The plugin only warns and continues on missing input, which would ship
# a release with degraded (or no) crash symbolication silently. Fail the
# build instead.
if Dir.glob("#{symbols_path}/**/*").select { |f| File.file?(f) }.empty?
UI.user_error!("Sentry upload: no native debug symbols found under #{symbols_path}")
end
unless File.exist?(dart_symbol_map_path)
UI.user_error!("Sentry upload: Dart obfuscation map not found at #{dart_symbol_map_path}")
end

sh("dart", "run", "sentry_dart_plugin",
"--sentry-define=symbols_path=#{symbols_path}",
"--sentry-define=dart_symbol_map_path=#{dart_symbol_map_path}"
)
end
end

# Runs tool/generate_release_info.dart (single source of truth for tag →
# marketing version + version code) and reads the values back from the
# generated Dart file. The generated file is the canonical artefact —
Expand Down Expand Up @@ -61,29 +104,48 @@ platform :android do
marketing_version = info["marketing_version"]
version_code = info["version_code"]

Dir.chdir("..") do
sh("flutter", "build", "appbundle",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info"
)
sh("flutter", "build", "apk",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info"
)
File.rename(
"../build/app/outputs/flutter-apk/app-release.apk",
"../build/app/outputs/flutter-apk/realunit-#{tag_name}.apk"
)
with_crash_reporting_defines do |defines_file|
Dir.chdir("..") do
sh("flutter", "build", "appbundle",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--dart-define-from-file=#{defines_file}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info/appbundle",
"--extra-gen-snapshot-options=--save-obfuscation-map=build/appbundle-obfuscation-map.json"
)
sh("flutter", "build", "apk",
"--release",
"--build-name=#{marketing_version}",
"--build-number=#{version_code}",
"--dart-define-from-file=#{defines_file}",
"--no-tree-shake-icons",
"--obfuscate",
"--split-debug-info=build/debug_info/apk",
"--extra-gen-snapshot-options=--save-obfuscation-map=build/apk-obfuscation-map.json"
)
File.rename(
"../build/app/outputs/flutter-apk/app-release.apk",
"../build/app/outputs/flutter-apk/realunit-#{tag_name}.apk"
)
end
end

upload_sentry_symbols(
marketing_version: marketing_version,
version_code: version_code,
symbols_path: "build/debug_info/appbundle",
dart_symbol_map_path: "build/appbundle-obfuscation-map.json"
)
upload_sentry_symbols(
marketing_version: marketing_version,
version_code: version_code,
symbols_path: "build/debug_info/apk",
dart_symbol_map_path: "build/apk-obfuscation-map.json"
)

# Upload the binary + changelog only. The changelog is tied to this
# build's version code (taken from the AAB); the listing metadata and
# images are pushed by the dedicated call below, keeping the two
Expand Down
68 changes: 67 additions & 1 deletion assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,45 @@
"or": "Oder",
"originalPdf": "Original-PDF",
"pay": "Bezahlen",
"payAwaitingSettlement": "Zahlung wird abgeschlossen",
"payConfirmButton": "Bezahlen",
"payFailureBitboxRequired": "Bitte verbinden Sie Ihre BitBox, um fortzufahren.",
"payFailureGeneric": "Bei der Zahlung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"payFailureInsufficientEth": "Es konnten nicht genügend ETH für die Netzwerkgebühren bereitgestellt werden. Bitte versuchen Sie es später erneut.",
"payFailureInsufficientZchf": "Ihr REALU-Bestand reicht für diesen Betrag nicht aus.",
"payFailureQuoteExpired": "Das Zahlungsangebot ist abgelaufen. Bitte scannen Sie den Code erneut.",
"payFailureSignatureUnsupported": "Diese Wallet kann keine Transaktionen signieren. Wechseln Sie zu einer Software- oder BitBox-Wallet.",
"payFailureTitle": "Zahlung fehlgeschlagen",
"paymentInformationFailed": "Beim Abrufen der Zahlungsinformationen ist ein Fehler aufgetreten.",
"paymentInformationFailedDescription": "Bitte versuchen Sie es später erneut. Wenn der Fehler weiterhin besteht, wenden Sie sich an unseren Support.",
"payoutAccountAdd": "Auszahlungskonto hinzufügen",
"payoutAccountSelect": "Auszahlungskonto auswählen",
"payPaying": "Zahlung wird gesendet",
"payPreparingSwap": "Tausch wird vorbereitet",
"payQuoteMerchant": "Empfänger",
"payQuoteRealuAmount": "Zu verkaufende REALU-Anteile",
"payQuoteRealuEstimated": "Erwarteter Erlös",
"payQuoteRealuFees": "Gebühren",
"payQuoteRequested": "Geforderter Betrag",
"payQuoteSummary": "Sie bezahlen ${amount} ${asset}",
"payQuoteTitle": "Zahlung bestätigen",
"payQuoteUnavailable": "Für diesen Zahlungscode ist keine ZCHF-Zahlung verfügbar.",
"payQuoteZchfNeeded": "Benötigte ZCHF",
"payRefreshingQuote": "Angebot wird aktualisiert",
"payRetryButton": "Zahlung erneut versuchen",
"payRetryInsufficientZchf": "Der Preis hat sich geändert und die getauschten ZCHF decken diese Zahlung nicht mehr. Ihre ZCHF bleiben in Ihrer Wallet – versuchen Sie es erneut, um ein neues Angebot zu erhalten.",
"payRetryQuoteExpired": "Das Zahlungsangebot ist vor dem Abschluss abgelaufen. Ihre getauschten ZCHF bleiben in Ihrer Wallet – versuchen Sie es erneut, um ein neues Angebot zu erhalten und zu bezahlen.",
"payRetryTitle": "Schließen Sie Ihre Zahlung ab",
"payRetryTransient": "Die Zahlung konnte nicht abgeschlossen werden, aber Ihre getauschten ZCHF bleiben in Ihrer Wallet. Versuchen Sie es erneut, um ohne erneuten Tausch zu bezahlen.",
"payRetryUnsignedTxMismatch": "Die vom Server gelieferten Zahlungsdetails stimmten nicht mit der Anfrage überein. Ihre getauschten ZCHF bleiben in Ihrer Wallet – bitte versuchen Sie es erneut.",
"payScanCameraPermissionDenied": "Für das Scannen eines Zahlungscodes wird Kamerazugriff benötigt. Bitte aktivieren Sie den Kamerazugriff für diese App in den Geräteeinstellungen.",
"payScanCameraUnavailable": "Die Kamera konnte nicht gestartet werden. Bitte versuchen Sie es erneut.",
"payScanInvalid": "Dies ist kein gültiger RealUnit-Zahlungscode.",
"payScanTitle": "Zahlungscode scannen",
"paySuccess": "Zahlung erfolgreich",
"paySuccessDescription": "Ihre Zahlung wurde abgeschlossen.",
"paySwapping": "REALU wird in ZCHF getauscht",
"payWaitingForEth": "Netzwerkgebühren werden angefordert",
"pdf": "PDF",
"pendingTransactions": "Ausstehende Transaktionen",
"personalData": "Persönliche Daten",
Expand All @@ -204,10 +239,11 @@
"pinVerifyFailed": "Die PIN ist falsch. Versuchen Sie es erneut.",
"pinVerifying": "Anmeldung…",
"pinVerifyLocked": "Zu viele Fehlversuche. Nutzen Sie 'PIN vergessen?', um zurückzusetzen.",
"pinVerifyLockedGate": "Zu viele Fehlversuche. Schließen Sie die App und starten Sie sie neu; auf dem Sperrbildschirm setzen Sie die Wallet über 'PIN vergessen?' zurück.",
"pinVerifyLockedTemporarily": "Zu viele Fehlversuche. Versuchen Sie es in ${remaining} erneut.",
"pinVerifySeedDescription": "Geben Sie Ihre PIN ein, um Ihre Seed-Phrase anzuzeigen",
"pinVerifyUnverifiable": "Ihre PIN kann nicht überprüft werden (Speicherfehler). Setzen Sie die Wallet über 'PIN vergessen?' zurück und stellen Sie sie mit Ihrer Seed-Phrase wieder her.",
"pinVerifyUnverifiableGate": "Ihre PIN kann nicht überprüft werden (Speicherfehler). Sperren Sie die App und setzen Sie die Wallet über den Sperrbildschirm zurück.",
"pinVerifyUnverifiableGate": "Ihre PIN kann nicht überprüft werden (Speicherfehler). Schließen Sie die App und starten Sie sie neu; auf dem Sperrbildschirm setzen Sie die Wallet über 'PIN vergessen?' zurück.",
"pleaseSelect": "Bitte auswählen",
"portfolio": "Bestand",
"portfolioDevelopment": "Bestandsentwicklung",
Expand Down Expand Up @@ -294,7 +330,37 @@
"sellReviewAndConfirm": "Verkauf prüfen & bestätigen",
"sellSuccess": "Verkauf erfolgreich",
"sellSuccessDescription": "Der Betrag wird Ihnen auf das angegebene Bankkonto ausgezahlt.",
"send": "Senden",
"sendAmountAvailable": "Verfügbar: ${shares} REALU",
"sendAmountInsufficient": "Nicht genügend REALU in Ihrer Wallet.",
"sendAmountInvalid": "Geben Sie eine ganze Anzahl REALU-Anteile ein.",
"sendAmountLabel": "Betrag in REALU-Anteilen",
"sendAmountTitle": "Zu sendender Betrag",
"sendConfirmAmount": "Betrag",
"sendConfirmButton": "REALU senden",
"sendConfirmRecipient": "Empfänger",
"sendConfirmSummary": "Sie senden ${shares} REALU",
"sendConfirmTitle": "Überweisung prüfen",
"sendFailureConfirmMismatch": "Die Überweisungsdetails stimmen nicht mehr mit Ihrer Bestätigung überein. Bitte gehen Sie zurück und versuchen Sie es erneut.",
"sendFailureGasUnavailable": "Überweisungen sind vorübergehend nicht verfügbar. Ihre REALU bleiben unangetastet – bitte versuchen Sie es später erneut.",
"sendFailureGeneric": "Bei der Überweisung ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
"sendFailureInvalidRequest": "Die Überweisung konnte nicht vorbereitet werden. Bitte prüfen Sie Empfänger und Betrag und versuchen Sie es erneut.",
"sendFailureRegistrationOrKycRequired": "Bitte schliessen Sie zuerst Ihre Registrierung oder Identitätsprüfung ab, um REALU zu senden.",
"sendFailureSignatureCancelled": "Die Signatur wurde abgebrochen. Ihre REALU bleiben unangetastet.",
"sendFailureSignatureUnsupported": "Diese Wallet kann keine gaslosen Überweisungen signieren. Wechseln Sie zu einer Software-Wallet, um REALU zu senden.",
"sendFailureTitle": "Überweisung fehlgeschlagen",
"sending": "Wird gesendet",
"sendPaste": "Einfügen",
"sendPreparing": "Überweisung wird vorbereitet",
"sendProcessTitle": "REALU wird gesendet",
"sendRecipientInvalid": "Dies ist keine gültige Wallet-Adresse.",
"sendRecipientLabel": "Empfängeradresse",
"sendRecipientManualHint": "Scannen Sie einen Wallet-QR-Code oder fügen Sie die Empfängeradresse unten ein.",
"sendRecipientTitle": "Empfänger scannen",
"sendShares": "${shares} REALU",
"sendSigning": "Bestätigen Sie die Überweisung in Ihrer Wallet",
"sendSuccess": "Überweisung gesendet",
"sendSuccessDescription": "Ihre REALU sind auf dem Weg zum Empfänger.",
"setNationalityFailed": "Ihre Staatsangehörigkeit konnte nicht gesetzt werden:\n${message}",
"settings": "Einstellungen",
"settingsAppVersion": "Version ${tag}",
Expand Down
Loading
Loading