Files
MyActivityPub/CONTRIBUTING.md
2026-01-24 17:45:29 +01:00

10 KiB

Contributing to My ActivityPub

Thank you for your interest in contributing to My ActivityPub! This document provides guidelines and instructions for contributing to the project.

Table of Contents

Code of Conduct

Our Pledge

We are committed to providing a welcoming and inspiring community for all. Please be respectful and constructive in your interactions.

Expected Behavior

  • Use welcoming and inclusive language
  • Be respectful of differing viewpoints
  • Accept constructive criticism gracefully
  • Focus on what is best for the community
  • Show empathy towards other community members

Getting Started

Prerequisites

Before you begin, ensure you have:

  • Android Studio Hedgehog (2023.1.1) or newer
  • JDK 11 or higher
  • Git installed and configured
  • Basic knowledge of Kotlin and Jetpack Compose
  • Familiarity with the Mastodon API

Finding Issues to Work On

  1. Check the Issues page
  2. Look for issues labeled good first issue or help wanted
  3. Comment on the issue to let others know you're working on it
  4. Wait for maintainer approval before starting work

Development Setup

1. Fork and Clone

# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/MyActivityPub.git
cd MyActivityPub

# Add the upstream repository
git remote add upstream https://github.com/ORIGINAL_OWNER/MyActivityPub.git

2. Create a Branch

# Create a new branch for your feature or bugfix
git checkout -b feature/your-feature-name

# Or for a bugfix
git checkout -b fix/bug-description

3. Set Up the Project

# Sync Gradle files
./gradlew build

# Run the app
./gradlew installDebug

How to Contribute

Reporting Bugs

Before creating a bug report:

  1. Check if the bug has already been reported
  2. Verify the bug exists in the latest version
  3. Collect relevant information (device, Android version, logs)

Bug Report Template:

**Description**
A clear description of the bug.

**Steps to Reproduce**
1. Step one
2. Step two
3. Step three

**Expected Behavior**
What should happen.

**Actual Behavior**
What actually happens.

**Environment**
- Device: [e.g., Pixel 6]
- Android Version: [e.g., Android 13]
- App Version: [e.g., 1.0.0]

**Logs**

Paste relevant logs here


**Screenshots**
If applicable, add screenshots.

Suggesting Enhancements

Enhancement suggestions are welcome! Please provide:

  1. Clear description of the enhancement
  2. Use cases and benefits
  3. Possible implementation approach
  4. Mockups or examples (if applicable)

Contributing Code

  1. Small Changes: Typos, bug fixes, small improvements can be submitted directly
  2. Large Changes: Open an issue first to discuss the change
  3. New Features: Must be discussed and approved before implementation

Coding Standards

Kotlin Style Guide

Follow the Official Kotlin Coding Conventions:

Naming

// Classes: PascalCase
class StatusCard { }

// Functions and variables: camelCase
fun loadTimeline() { }
val statusCount = 10

// Constants: UPPER_SNAKE_CASE
const val MAX_RETRIES = 3

// Private properties: leading underscore
private val _uiState = MutableStateFlow()

Formatting

// Use 4 spaces for indentation
class Example {
    fun method() {
        if (condition) {
            // code here
        }
    }
}

// Line length: max 120 characters
// Break long function signatures:
fun longFunctionName(
    parameter1: String,
    parameter2: Int,
    parameter3: Boolean
): ReturnType {
    // implementation
}

Comments and Documentation

/**
 * KDoc for public APIs
 * 
 * @param userId The user identifier
 * @return The user's timeline
 */
suspend fun getUserTimeline(userId: String): Result<List<Status>> {
    // Implementation comments for complex logic
    val result = apiService.getTimeline(userId)
    return parseResult(result)
}

Compose Best Practices

// Composable function names: PascalCase
@Composable
fun StatusCard(status: Status, modifier: Modifier = Modifier) {
    // Always provide Modifier parameter
    // Default to Modifier
}

// Extract complex composables
@Composable
private fun StatusHeader(account: Account) {
    // Smaller, focused components
}

// Use remember for expensive operations
val formattedDate = remember(timestamp) {
    formatDate(timestamp)
}

// Use derivedStateOf for computed values
val isExpanded by remember {
    derivedStateOf { height > maxHeight }
}

Architecture Guidelines

  1. Separation of Concerns: Each class has a single responsibility
  2. MVVM Pattern: Follow the established architecture
  3. Repository Pattern: All data access through repositories
  4. State Management: Use StateFlow for UI state
  5. Error Handling: Always handle errors gracefully

File Organization

app/src/main/java/com/manalejandro/myactivitypub/
├── MainActivity.kt                    # Entry point
├── data/
│   ├── api/                          # API interfaces
│   ├── models/                       # Data models
│   └── repository/                   # Repository implementations
└── ui/
    ├── components/                   # Reusable UI components
    ├── screens/                      # Full screen composables
    ├── viewmodel/                    # ViewModels
    └── theme/                        # Theme configuration

Commit Guidelines

Commit Message Format

<type>(<scope>): <subject>

<body>

<footer>

Types

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, missing semicolons, etc.)
  • refactor: Code refactoring
  • test: Adding or updating tests
  • chore: Maintenance tasks

Examples

feat(timeline): add pull-to-refresh functionality

Implemented SwipeRefresh composable for the timeline screen.
Users can now pull down to refresh the timeline.

Closes #123

---

fix(statuscard): correct avatar image loading

Fixed issue where avatar images weren't loading correctly
due to missing Coil configuration.

Fixes #456

---

docs(readme): update installation instructions

Added more detailed steps for building the project
and troubleshooting common issues.

Guidelines

  • Use present tense ("add feature" not "added feature")
  • Keep subject line under 50 characters
  • Capitalize the subject line
  • Don't end the subject line with a period
  • Use the body to explain what and why, not how
  • Reference issues and pull requests in the footer

Pull Request Process

Before Submitting

  1. Test your changes: Ensure the app builds and runs
  2. Run lint checks: ./gradlew lint
  3. Update documentation: If you changed APIs or features
  4. Add tests: For new features or bug fixes
  5. Update CHANGELOG: Add your changes to the unreleased section

Submitting a Pull Request

  1. Push your branch:
git push origin feature/your-feature-name
  1. Create Pull Request on GitHub with:

    • Clear title describing the change
    • Detailed description of what and why
    • Link to related issues
    • Screenshots/recordings for UI changes
    • Test instructions
  2. PR Template:

## Description
Brief description of changes.

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Related Issues
Closes #123

## Testing
- [ ] Tested on physical device
- [ ] Tested on emulator
- [ ] Added unit tests
- [ ] Added UI tests

## Screenshots
[Add screenshots if applicable]

## Checklist
- [ ] Code follows style guidelines
- [ ] Self-reviewed the code
- [ ] Commented complex code
- [ ] Updated documentation
- [ ] No new warnings
- [ ] Added tests
- [ ] All tests pass

Review Process

  1. Maintainer will review your PR
  2. Address any requested changes
  3. Once approved, your PR will be merged
  4. Delete your branch after merge

Testing

Running Tests

# Run all tests
./gradlew test

# Run unit tests
./gradlew testDebugUnitTest

# Run instrumented tests
./gradlew connectedAndroidTest

Writing Tests

Unit Tests

class TimelineViewModelTest {
    @Test
    fun `loadTimeline updates state to Success on successful fetch`() = runTest {
        // Arrange
        val mockRepository = mock<MastodonRepository>()
        val testStatuses = listOf(/* test data */)
        whenever(mockRepository.getPublicTimeline())
            .thenReturn(Result.success(testStatuses))
        
        val viewModel = TimelineViewModel(mockRepository)
        
        // Act
        viewModel.loadTimeline()
        
        // Assert
        val state = viewModel.uiState.value
        assertTrue(state is TimelineUiState.Success)
        assertEquals(testStatuses, (state as TimelineUiState.Success).statuses)
    }
}

Compose UI Tests

class StatusCardTest {
    @get:Rule
    val composeTestRule = createComposeRule()
    
    @Test
    fun statusCard_displays_username() {
        val testStatus = Status(/* test data */)
        
        composeTestRule.setContent {
            StatusCard(status = testStatus)
        }
        
        composeTestRule
            .onNodeWithText(testStatus.account.username)
            .assertIsDisplayed()
    }
}

Documentation

Code Documentation

  • Add KDoc comments for all public APIs
  • Comment complex algorithms
  • Use meaningful variable and function names
  • Update README.md for user-facing changes

Documentation Files

  • README.md: User documentation, setup, features
  • ARCHITECTURE.md: Architecture and design decisions
  • API.md: API integration details
  • CONTRIBUTING.md: This file

Questions?

If you have questions:

  1. Check existing documentation
  2. Search closed issues
  3. Ask in discussions
  4. Open a new issue with the question label

Recognition

Contributors will be recognized in:

  • CONTRIBUTORS.md file
  • Release notes
  • Project README

Thank you for contributing to My ActivityPub! 🎉