diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 248b3a8..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 @@ -85,7 +93,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 @@ -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 (includes waiting for processing and public beta distribution) - 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 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 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 Public Beta!" + 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 "📧 External testers will be notified via email" \ 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/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 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index daf379c..f0f1c12 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") @@ -107,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!") @@ -166,31 +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: false, + 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: true, # Distribute to external testers (public beta) + distribute_external: is_public_beta, # Internal testing by default, external for 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 + } + + # 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(