From 630bd20eb8b77f6b8c503b41fb03a3de045905c2 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Sat, 18 Oct 2025 16:12:55 +0800 Subject: [PATCH 1/3] fix: Initialize animatedOnlineCount when view appears with stats Problem: - In v1.1.4, online count always shows as 0 - animatedOnlineCount is initialized to 0 as default - When onlineStats changes from nil to a value, animatedOnlineCount stays at 0 - onChange only triggers when the value changes, not on initial load Solution: - Add onAppear to initialize animatedOnlineCount from stats.onlineCount - Check if animatedOnlineCount is 0 and stats has valid count - This ensures the count displays correctly on first load Fixes the regression introduced in PR #57 where animated count feature broke the initial display of online user count. --- V2er/View/Widget/Updatable/HeadIndicatorView.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/V2er/View/Widget/Updatable/HeadIndicatorView.swift b/V2er/View/Widget/Updatable/HeadIndicatorView.swift index 5053af1..d2b9da3 100644 --- a/V2er/View/Widget/Updatable/HeadIndicatorView.swift +++ b/V2er/View/Widget/Updatable/HeadIndicatorView.swift @@ -58,6 +58,12 @@ struct HeadIndicatorView: View { .foregroundColor(.secondaryText) } } + .onAppear { + // Initialize animatedOnlineCount when view appears with valid stats + if animatedOnlineCount == 0 && stats.onlineCount > 0 { + animatedOnlineCount = stats.onlineCount + } + } .onChange(of: stats.onlineCount) { newValue in withAnimation(.easeInOut(duration: 0.3)) { animatedOnlineCount = newValue From caffb4c7fb9a1e22b930fabb3fff5d861abc770c Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Sat, 18 Oct 2025 16:19:20 +0800 Subject: [PATCH 2/3] chore: Change default TestFlight release to internal testing - Set distribute_external to false (internal testing only) - Remove public beta groups and external tester notifications - Skip beta review submission for internal builds - Update workflow and lane descriptions - Simplify upload_to_testflight parameters This allows faster iteration and testing without requiring Beta App Review approval for each build. Public beta releases can still be done manually through App Store Connect when needed. --- .github/workflows/release.yml | 14 +++++------ fastlane/Fastfile | 44 +++++++---------------------------- 2 files changed, 16 insertions(+), 42 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 248b3a8..b9f1524 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: echo "✅ Successfully created tag: $TAG_NAME" build-and-release: - name: Build and Release to TestFlight (Public Beta) + name: Build and Release to TestFlight (Internal Testing) needs: version-check if: needs.version-check.outputs.should_release == 'true' runs-on: macos-latest @@ -291,7 +291,7 @@ jobs: export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 - # Run the beta lane (includes waiting for processing and public beta distribution) + # Run the beta lane (uploads to TestFlight for internal testing) fastlane beta - name: Create GitHub Release @@ -303,9 +303,9 @@ jobs: ## 🚀 Version ${{ needs.version-check.outputs.version }} Build: ${{ needs.version-check.outputs.build }} - ### TestFlight Public Beta - This version has been automatically submitted to TestFlight for public beta testing. - External testers will receive email notifications when the build is available. + ### TestFlight Internal Testing + This version has been automatically uploaded to TestFlight for internal testing. + The build is available to internal testers immediately after processing. ### What's New - See [commit history](https://github.com/${{ github.repository }}/commits/${{ needs.version-check.outputs.new_tag }}) for changes @@ -318,7 +318,7 @@ jobs: - name: Post release notification if: success() run: | - echo "✅ Successfully released version ${{ needs.version-check.outputs.version }} to TestFlight Public Beta!" + echo "✅ Successfully released version ${{ needs.version-check.outputs.version }} to TestFlight Internal Testing!" echo "🏷️ Tag: ${{ needs.version-check.outputs.new_tag }}" echo "🔢 Build: ${{ needs.version-check.outputs.build }}" - echo "📧 External testers will be notified via email" \ No newline at end of file + echo "👥 Internal testers can access the build immediately after processing" \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index daf379c..c27d314 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -67,7 +67,7 @@ platform :ios do ) end - desc "Distribute existing build to beta testers" + desc "Distribute existing build to internal testers" lane :distribute_beta do |options| # Get App Store Connect API key api_key = get_api_key @@ -76,30 +76,17 @@ platform :ios do build_number = options[:build_number] begin - # Distribute to beta testers + # Distribute to internal testers only testflight( api_key: api_key, app_identifier: "v2er.app", - skip_submission: false, - distribute_external: true, # Distribute to external testers (public beta) - groups: ["Public Beta", "External Testers", "Beta Testers"], # Public beta groups - notify_external_testers: true, # Send email notifications - uses_non_exempt_encryption: false, - submit_beta_review: true, # Automatically submit for Beta review + skip_submission: true, # Skip beta review for internal testing + distribute_external: false, # Internal testing only (not public beta) wait_for_uploaded_build: true, - beta_app_description: "V2er is an elegant third-party client for V2EX forum", - beta_app_feedback_email: "support@v2er.app", - demo_account_required: false, - beta_app_review_info: { - contact_email: "support@v2er.app", - contact_first_name: "V2er", - contact_last_name: "Support", - contact_phone: "+86 13800138000", - notes: "This is a third-party client app for V2EX forum. No special account needed for testing." - } + uses_non_exempt_encryption: false ) - UI.success("✅ Successfully distributed build to beta testers!") + UI.success("✅ Successfully distributed build to internal testers!") rescue => e UI.error("Failed to distribute: #{e.message}") UI.message("You may need to manually distribute the build in App Store Connect") @@ -169,27 +156,14 @@ platform :ios do # Upload to TestFlight upload_to_testflight( api_key: api_key, - skip_submission: false, + skip_submission: true, # Skip beta review submission for internal testing skip_waiting_for_build_processing: false, # Wait for processing before distribution wait_processing_interval: 30, # Check every 30 seconds wait_processing_timeout_duration: 900, # Wait up to 15 minutes for processing - distribute_external: true, # Distribute to external testers (public beta) + distribute_external: false, # Internal testing only (not public beta) distribute_only: false, # Upload and distribute in one action - groups: ["Public Beta", "External Testers", "Beta Testers"], # Public beta groups changelog: changelog_content, # Use changelog from CHANGELOG.md - notify_external_testers: true, # Send email notifications to external testers - uses_non_exempt_encryption: false, # Required for automatic distribution - submit_beta_review: true, # Automatically submit for Beta review - beta_app_description: "V2er is an elegant third-party client for V2EX forum", - beta_app_feedback_email: "support@v2er.app", - demo_account_required: false, # No demo account required - beta_app_review_info: { - contact_email: "support@v2er.app", - contact_first_name: "V2er", - contact_last_name: "Support", - contact_phone: "+86 13800138000", - notes: "This is a third-party client app for V2EX forum. No special account needed for testing." - } + uses_non_exempt_encryption: false # Required export compliance ) # Notify success From 3a2b74018d520e675ede74f387f040751b9a7b70 Mon Sep 17 00:00:00 2001 From: Gray Zhang Date: Sat, 18 Oct 2025 16:23:30 +0800 Subject: [PATCH 3/3] feat: Add configurable release channel parameter - Add 'release_channel' workflow input (internal/public_beta) - Default to 'internal' for automatic releases - Support 'public_beta' for manual workflow_dispatch - Fastlane beta lane now accepts channel parameter - Dynamically configure TestFlight distribution based on channel: - internal: No beta review, internal testers only - public_beta: Submit for beta review, notify external testers - Update GitHub Release notes to reflect selected channel - Update notification messages based on channel Usage: - Automatic (push to main): Uses 'internal' by default - Manual workflow: Can select 'public_beta' in workflow dispatch UI - Local fastlane: fastlane beta channel:public_beta --- .github/workflows/release.yml | 36 +++++++++++++++++++----- fastlane/Fastfile | 52 +++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9f1524..09b8a29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,6 +13,14 @@ on: required: false default: false type: boolean + release_channel: + description: 'TestFlight release channel' + required: false + default: 'internal' + type: choice + options: + - internal + - public_beta env: DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer @@ -283,6 +291,7 @@ jobs: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }} TEAM_ID: ${{ secrets.TEAM_ID }} + RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'internal' }} run: | # Enable verbose output for debugging export FASTLANE_VERBOSE=true @@ -291,11 +300,15 @@ jobs: export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 - # Run the beta lane (uploads to TestFlight for internal testing) - fastlane beta + # Run the beta lane with release channel parameter + # Default to 'internal' for automatic releases (push to main) + # Can be set to 'public_beta' for manual workflow_dispatch + fastlane beta channel:$RELEASE_CHANNEL - name: Create GitHub Release uses: softprops/action-gh-release@v1 + env: + RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'internal' }} with: tag_name: ${{ needs.version-check.outputs.new_tag }} name: Release ${{ needs.version-check.outputs.version }} @@ -303,9 +316,10 @@ jobs: ## 🚀 Version ${{ needs.version-check.outputs.version }} Build: ${{ needs.version-check.outputs.build }} - ### TestFlight Internal Testing - This version has been automatically uploaded to TestFlight for internal testing. - The build is available to internal testers immediately after processing. + ### TestFlight Distribution + **Channel**: ${{ github.event.inputs.release_channel || 'internal' }} + + ${{ github.event.inputs.release_channel == 'public_beta' && '📧 **Public Beta**: This build has been submitted to TestFlight for public beta testing. External testers will receive email notifications when the build is available after beta review approval.' || '👥 **Internal Testing**: This build is available to internal testers immediately after processing. No beta review required.' }} ### What's New - See [commit history](https://github.com/${{ github.repository }}/commits/${{ needs.version-check.outputs.new_tag }}) for changes @@ -317,8 +331,16 @@ jobs: - name: Post release notification if: success() + env: + RELEASE_CHANNEL: ${{ github.event.inputs.release_channel || 'internal' }} run: | - echo "✅ Successfully released version ${{ needs.version-check.outputs.version }} to TestFlight Internal Testing!" + CHANNEL_DISPLAY=$([ "$RELEASE_CHANNEL" = "public_beta" ] && echo "Public Beta" || echo "Internal Testing") + echo "✅ Successfully released version ${{ needs.version-check.outputs.version }} to TestFlight $CHANNEL_DISPLAY!" echo "🏷️ Tag: ${{ needs.version-check.outputs.new_tag }}" echo "🔢 Build: ${{ needs.version-check.outputs.build }}" - echo "👥 Internal testers can access the build immediately after processing" \ No newline at end of file + echo "📦 Channel: $RELEASE_CHANNEL" + if [ "$RELEASE_CHANNEL" = "public_beta" ]; then + echo "📧 External testers will be notified after beta review approval" + else + echo "👥 Internal testers can access the build immediately after processing" + fi \ No newline at end of file diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c27d314..f0f1c12 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -94,7 +94,20 @@ platform :ios do end desc "Build and upload to TestFlight" - lane :beta do + desc "Parameters:" + desc " channel: 'internal' (default) or 'public_beta'" + lane :beta do |options| + # Get release channel from options or environment variable + # Default to 'internal' if not specified + channel = options[:channel] || ENV['RELEASE_CHANNEL'] || 'internal' + + UI.message("📦 Release channel: #{channel}") + + # Validate channel parameter + unless ['internal', 'public_beta'].include?(channel) + UI.user_error!("Invalid channel: #{channel}. Must be 'internal' or 'public_beta'") + end + # Validate that changelog exists for current version unless ChangelogHelper.validate_changelog_exists UI.user_error!("Please update CHANGELOG.md with an entry for the current version before releasing!") @@ -153,18 +166,45 @@ platform :ios do # Extract changelog for the current version changelog_content = ChangelogHelper.extract_changelog(current_version) - # Upload to TestFlight - upload_to_testflight( + # Configure TestFlight upload based on release channel + is_public_beta = channel == 'public_beta' + + upload_params = { api_key: api_key, - skip_submission: true, # Skip beta review submission for internal testing + skip_submission: !is_public_beta, # Skip beta review for internal, submit for public beta skip_waiting_for_build_processing: false, # Wait for processing before distribution wait_processing_interval: 30, # Check every 30 seconds wait_processing_timeout_duration: 900, # Wait up to 15 minutes for processing - distribute_external: false, # Internal testing only (not public beta) + distribute_external: is_public_beta, # Internal testing by default, external for public beta distribute_only: false, # Upload and distribute in one action changelog: changelog_content, # Use changelog from CHANGELOG.md uses_non_exempt_encryption: false # Required export compliance - ) + } + + # Add public beta specific parameters + if is_public_beta + upload_params.merge!({ + groups: ["Public Beta", "External Testers", "Beta Testers"], # Public beta groups + notify_external_testers: true, # Send email notifications to external testers + submit_beta_review: true, # Automatically submit for Beta review + beta_app_description: "V2er is an elegant third-party client for V2EX forum", + beta_app_feedback_email: "support@v2er.app", + demo_account_required: false, # No demo account required + beta_app_review_info: { + contact_email: "support@v2er.app", + contact_first_name: "V2er", + contact_last_name: "Support", + contact_phone: "+86 13800138000", + notes: "This is a third-party client app for V2EX forum. No special account needed for testing." + } + }) + UI.message("📧 Public beta mode: Will notify external testers and submit for beta review") + else + UI.message("👥 Internal testing mode: No beta review submission required") + end + + # Upload to TestFlight + upload_to_testflight(upload_params) # Notify success notification(