Skip to main content

Overview

The QuestCardsComponent is the core component for displaying and interacting with quests in Tareas. It provides a swipeable card interface with smooth animations, skeleton loading states, and category filtering capabilities.

Features

  • Swipe Gestures: Swipe right to complete quests, swipe left to delete
  • Skeleton Loading: Displays skeleton screens while quests are loading
  • Category Filtering: Filter quests by categories using horizontal category selector
  • Visual Feedback: Dynamic feedback icons and colors during swipe actions
  • Completion Animations: Smooth transitions when completing or deleting quests
  • Reactive State: Uses RxJS BehaviorSubject for state management

Component Structure

import { QuestCardsComponent } from './components/quest-cards/quest-cards.component';

@Component({
  selector: 'app-quest-cards',
  standalone: true,
  imports: [CommonModule, IonicModule, CardCategoryHorizontallyComponent],
  templateUrl: './quest-cards.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class QuestCardsComponent implements OnInit {
  state$: Observable<ComponentState>;
  selectedCategoryNames: string[] = [];
  
  constructor(
    private gestureCtrl: GestureController,
    private questService: QuestService,
    private cdr: ChangeDetectorRef,
    private questSharedService: QuestSharedService
  ) { }
}

Key Methods

loadQuests()

Loads and filters quests based on selected categories.
public loadQuests(): void {
  console.log('Loading quests...');
  this.applyCategoryFilter();
}

onCategorySelected()

Handles category selection from the horizontal category component.
onCategorySelected(categories: Category[]) {
  this.selectedCategoryKeys = categories.map(c => c.name);
  this.applyCategoryFilter();
}

handleSwipe()

Processes swipe gestures to complete or delete quests.
handleSwipe(ev: any, quest: Quest, cardEl: HTMLElement) {
  const threshold = 150; // px to consider swipe
  
  if (ev.deltaX > threshold) {
    // Swipe right: complete quest
    this.questService.completeQuest(quest.id);
    cardEl.style.transform = 'translateX(100%)';
  } else if (ev.deltaX < -threshold) {
    // Swipe left: delete quest
    this.questService.deleteQuest(quest.id);
    cardEl.style.transform = 'translateX(-100%)';
  }
}

State Management

The component uses a BehaviorSubject to manage state:
private stateSubject = new BehaviorSubject<ComponentState>({
  activeTab: 'daily',
  userXp: 65,
  isLoading: true,
  processedQuests: []
});

state$: Observable<ComponentState> = this.stateSubject.asObservable();

Swipe Gestures

Swipe gestures are implemented using Ionic’s GestureController:
ngAfterViewInit() {
  this.cardWrappers.forEach((cardRef, index) => {
    const gesture: Gesture = this.gestureCtrl.create({
      el: cardRef.nativeElement,
      gestureName: 'swipe',
      onMove: ev => {
        // Move card and show feedback
        cardRef.nativeElement.style.transform = 
          `translateX(${ev.deltaX}px) rotate(${ev.deltaX / 20}deg)`;
        
        if (ev.deltaX < 0) {
          // Show delete feedback (red)
          feedbackEl.style.backgroundColor = 'red';
          feedbackEl.querySelector('ion-icon')!.setAttribute('name', 'trash');
        } else if (ev.deltaX > 0) {
          // Show complete feedback (green)
          feedbackEl.style.backgroundColor = 'green';
          feedbackEl.querySelector('ion-icon')!.setAttribute('name', 'checkmark');
        }
      },
      onEnd: ev => this.handleSwipe(ev, quest, cardRef.nativeElement)
    });
    gesture.enable(true);
  });
}

Quest Card Structure

Each quest card displays:
  • Category Badge: Shows the quest category
  • Difficulty Stars: Flame icons indicating difficulty (1-3)
  • Quest Icon: Category-specific icon
  • Title & Description: Quest details
  • Reward Section: XP rewards and badges
  • Progress Bar: Shows completion progress (if applicable)
  • Completion Overlay: Displayed when quest is completed

Usage Example

import { QuestCardsComponent } from './components/quest-cards/quest-cards.component';

@Component({
  template: `
    <app-quest-cards></app-quest-cards>
  `
})
export class QuestsPage {}

Integration with Services

The component integrates with:
  • QuestService: For CRUD operations on quests
  • QuestSharedService: For refresh notifications across components
  • CategoryService: For category filtering
// Subscribe to refresh events
this.questSharedService.refresh$.subscribe(() => {
  this.applyCategoryFilter();
});

// Subscribe to quest updates
this.questService.getQuests().subscribe(() => {
  this.applyCategoryFilter();
});

Performance Optimizations

The component uses ChangeDetectionStrategy.OnPush for better performance and trackBy functions to optimize rendering.
trackByQuestId(index: number, quest: Quest): string {
  return quest.id;
}

Difficulty Display

Difficulty is displayed using flame icons:
getDifficultyArray(difficulty: number): boolean[] {
  return [
    difficulty >= 1,
    difficulty >= 2,
    difficulty >= 3
  ];
}

Styling Classes

The component uses realm-specific color classes:
  • realm-green: Code/Development quests
  • realm-purple: Design quests
  • realm-orange: Admin/DevOps quests
  • realm-blue: Mind/Learning quests

Build docs developers (and LLMs) love