initial commit

Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2026-01-24 17:45:29 +01:00
commit 8c7417f913
Se han modificado 73 ficheros con 6362 adiciones y 0 borrados

337
docs/API.md Archivo normal
Ver fichero

@@ -0,0 +1,337 @@
# API Documentation
## Overview
This document describes the Mastodon API integration used in My ActivityPub app. The app communicates with Mastodon-compatible servers using their REST API v1.
## Base URL
Default: `https://mastodon.social/`
You can configure this to any Mastodon or ActivityPub-compatible instance.
## Authentication
Currently, the app accesses public endpoints that don't require authentication. Future versions will implement OAuth 2.0 for authenticated requests.
## Endpoints
### 1. Get Public Timeline
Retrieve statuses from the public timeline.
**Endpoint**: `GET /api/v1/timelines/public`
**Parameters**:
- `limit` (integer, optional): Maximum number of results. Default: 20
- `local` (boolean, optional): Show only local statuses. Default: false
- `max_id` (string, optional): Return results older than this ID
- `since_id` (string, optional): Return results newer than this ID
- `min_id` (string, optional): Return results immediately newer than this ID
**Example Request**:
```
GET https://mastodon.social/api/v1/timelines/public?limit=20&local=false
```
**Response**: Array of Status objects
**Example Response**:
```json
[
{
"id": "109382159165398564",
"created_at": "2022-11-23T07:49:01.940Z",
"content": "<p>Hello world!</p>",
"account": {
"id": "109382",
"username": "alice",
"display_name": "Alice",
"avatar": "https://...",
...
},
"media_attachments": [],
"favourites_count": 5,
"reblogs_count": 2,
"replies_count": 1,
...
}
]
```
### 2. Get Instance Information
Get information about the Mastodon instance.
**Endpoint**: `GET /api/v1/instance`
**Parameters**: None
**Example Request**:
```
GET https://mastodon.social/api/v1/instance
```
**Response**: Instance object
**Example Response**:
```json
{
"uri": "mastodon.social",
"title": "Mastodon",
"short_description": "The original server operated by the Mastodon gGmbH non-profit",
"description": "...",
"version": "4.0.0",
"languages": ["en"],
"thumbnail": "https://..."
}
```
### 3. Get Account
Get account information.
**Endpoint**: `GET /api/v1/accounts/:id`
**Parameters**:
- `id` (string, required): Account ID
**Example Request**:
```
GET https://mastodon.social/api/v1/accounts/109382
```
**Response**: Account object
**Example Response**:
```json
{
"id": "109382",
"username": "alice",
"acct": "alice",
"display_name": "Alice",
"locked": false,
"bot": false,
"created_at": "2022-11-23T07:49:01.940Z",
"note": "<p>Bio goes here</p>",
"url": "https://mastodon.social/@alice",
"avatar": "https://...",
"header": "https://...",
"followers_count": 100,
"following_count": 50,
"statuses_count": 500
}
```
### 4. Get Account Statuses
Get statuses posted by an account.
**Endpoint**: `GET /api/v1/accounts/:id/statuses`
**Parameters**:
- `id` (string, required): Account ID
- `limit` (integer, optional): Maximum number of results. Default: 20
- `max_id` (string, optional): Return results older than this ID
- `since_id` (string, optional): Return results newer than this ID
- `exclude_replies` (boolean, optional): Skip statuses that reply to other statuses
- `exclude_reblogs` (boolean, optional): Skip statuses that are reblogs of other statuses
- `only_media` (boolean, optional): Show only statuses with media attached
**Example Request**:
```
GET https://mastodon.social/api/v1/accounts/109382/statuses?limit=20
```
**Response**: Array of Status objects
## Data Models
### Status
Represents a post/toot on Mastodon.
**Properties**:
```typescript
{
id: string // Unique identifier
created_at: string // ISO 8601 datetime
content: string // HTML content
account: Account // Account that posted this status
media_attachments: Array // Media attachments
reblog: Status | null // If this is a reblog, the original status
favourites_count: number // Number of favorites
reblogs_count: number // Number of reblogs/boosts
replies_count: number // Number of replies
favourited: boolean // Has the current user favorited this?
reblogged: boolean // Has the current user reblogged this?
url: string | null // URL to the status
visibility: string // Visibility level (public, unlisted, private, direct)
}
```
### Account
Represents a user account.
**Properties**:
```typescript
{
id: string // Unique identifier
username: string // Username (without @domain)
acct: string // Full username (@username@domain)
display_name: string // Display name
avatar: string // URL to avatar image
header: string // URL to header image
note: string // Bio/description (HTML)
url: string | null // URL to profile page
followers_count: number // Number of followers
following_count: number // Number of accounts being followed
statuses_count: number // Number of statuses posted
bot: boolean // Is this a bot account?
locked: boolean // Does this account require follow requests?
}
```
### MediaAttachment
Represents media files attached to statuses.
**Properties**:
```typescript
{
id: string // Unique identifier
type: string // Type: image, video, gifv, audio, unknown
url: string // URL to the media file
preview_url: string | null // URL to the preview/thumbnail
remote_url: string | null // Remote URL if the media is from another server
description: string | null // Alt text description
}
```
### Instance
Represents a Mastodon instance.
**Properties**:
```typescript
{
uri: string // Domain name
title: string // Instance name
description: string // Long description (HTML)
short_description: string // Short description (plaintext)
version: string // Mastodon version
thumbnail: string | null // URL to thumbnail image
languages: Array<string> // ISO 639 Part 1-5 language codes
email: string | null // Contact email
contact_account: Account // Contact account
}
```
## Error Handling
The API returns standard HTTP status codes:
- `200 OK` - Request succeeded
- `400 Bad Request` - Invalid parameters
- `401 Unauthorized` - Authentication required
- `403 Forbidden` - Access denied
- `404 Not Found` - Resource not found
- `429 Too Many Requests` - Rate limit exceeded
- `500 Internal Server Error` - Server error
- `503 Service Unavailable` - Server temporarily unavailable
**Error Response Format**:
```json
{
"error": "Error message here"
}
```
## Rate Limiting
Mastodon implements rate limiting to prevent abuse. Limits vary by instance but typically:
- 300 requests per 5 minutes for authenticated requests
- Lower limits for unauthenticated requests
Rate limit information is returned in response headers:
- `X-RateLimit-Limit` - Maximum number of requests
- `X-RateLimit-Remaining` - Remaining requests in current window
- `X-RateLimit-Reset` - Time when the limit resets (ISO 8601)
## Best Practices
1. **Respect Rate Limits**: Implement exponential backoff when hitting rate limits
2. **Cache Responses**: Cache instance info and other static data
3. **Use Pagination**: Use `max_id` and `since_id` for efficient pagination
4. **Handle Errors**: Always handle network errors and API errors gracefully
5. **Validate Input**: Validate user input before making API calls
6. **Use HTTPS**: Always use HTTPS for API requests
7. **Set User-Agent**: Include a descriptive User-Agent header
## Implementation in the App
### Service Layer
`MastodonApiService.kt` defines the API interface using Retrofit annotations:
```kotlin
interface MastodonApiService {
@GET("api/v1/timelines/public")
suspend fun getPublicTimeline(
@Query("limit") limit: Int = 20,
@Query("local") local: Boolean = false
): Response<List<Status>>
// Other endpoints...
}
```
### Repository Layer
`MastodonRepository.kt` wraps API calls with error handling:
```kotlin
suspend fun getPublicTimeline(limit: Int = 20, local: Boolean = false): Result<List<Status>> {
return withContext(Dispatchers.IO) {
try {
val response = apiService.getPublicTimeline(limit, local)
if (response.isSuccessful) {
Result.success(response.body() ?: emptyList())
} else {
Result.failure(Exception("Error: ${response.code()}"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}
```
### ViewModel Layer
`TimelineViewModel.kt` manages UI state and calls repository methods:
```kotlin
fun loadTimeline() {
viewModelScope.launch {
_uiState.value = TimelineUiState.Loading
repository.getPublicTimeline().fold(
onSuccess = { statuses ->
_uiState.value = TimelineUiState.Success(statuses)
},
onFailure = { error ->
_uiState.value = TimelineUiState.Error(error.message ?: "Unknown error")
}
)
}
}
```
## Further Reading
- [Official Mastodon API Documentation](https://docs.joinmastodon.org/api/)
- [ActivityPub Specification](https://www.w3.org/TR/activitypub/)
- [Retrofit Documentation](https://square.github.io/retrofit/)
- [Kotlin Coroutines Guide](https://kotlinlang.org/docs/coroutines-guide.html)

476
docs/ARCHITECTURE.md Archivo normal
Ver fichero

@@ -0,0 +1,476 @@
# Architecture Documentation
## Overview
My ActivityPub follows the **MVVM (Model-View-ViewModel)** architecture pattern combined with **Repository pattern** for clean separation of concerns and testability.
## Architecture Layers
```
┌─────────────────────────────────────────────────┐
│ UI Layer (View) │
│ Jetpack Compose Composables │
└──────────────────┬──────────────────────────────┘
│ observes StateFlow
┌─────────────────────────────────────────────────┐
│ ViewModel Layer │
│ Business Logic & State Management │
└──────────────────┬──────────────────────────────┘
│ calls methods
┌─────────────────────────────────────────────────┐
│ Repository Layer │
│ Data Operations Abstraction │
└──────────────────┬──────────────────────────────┘
│ makes API calls
┌─────────────────────────────────────────────────┐
│ Data Source Layer │
│ API Service (Retrofit) │
└──────────────────┬──────────────────────────────┘
│ HTTP requests
┌─────────────────────────────────────────────────┐
│ Remote Server │
│ Mastodon/ActivityPub API │
└─────────────────────────────────────────────────┘
```
## Layer Details
### 1. UI Layer (View)
**Location**: `ui/components/`, `MainActivity.kt`
**Responsibilities**:
- Display data to the user
- Capture user interactions
- Observe ViewModel state
- Render UI using Jetpack Compose
**Key Components**:
#### MainActivity.kt
The entry point of the application. Sets up the Compose UI, dependency injection (manual), and hosts the main composable.
```kotlin
@Composable
fun MyActivityPubApp() {
// Setup dependencies
val repository = MastodonRepository(apiService)
val viewModel: TimelineViewModel = viewModel { TimelineViewModel(repository) }
// Observe state
val uiState by viewModel.uiState.collectAsState()
// Render UI based on state
when (uiState) {
is TimelineUiState.Loading -> LoadingUI()
is TimelineUiState.Success -> TimelineUI(statuses)
is TimelineUiState.Error -> ErrorUI(message)
}
}
```
#### StatusCard.kt
Reusable composable component for displaying individual status posts.
**Features**:
- User avatar and information
- HTML content rendering
- Media attachment display
- Interaction buttons (reply, boost, favorite)
- Timestamp formatting
### 2. ViewModel Layer
**Location**: `ui/viewmodel/`
**Responsibilities**:
- Manage UI state
- Handle business logic
- Coordinate data operations
- Expose data streams to UI
- Survive configuration changes
**Key Components**:
#### TimelineViewModel.kt
Manages the timeline screen state and operations.
**State Management**:
```kotlin
sealed class TimelineUiState {
object Loading : TimelineUiState()
data class Success(val statuses: List<Status>) : TimelineUiState()
data class Error(val message: String) : TimelineUiState()
}
```
**Flow**:
1. UI subscribes to `uiState` StateFlow
2. ViewModel fetches data from repository
3. ViewModel updates state based on result
4. UI recomposes with new state
**Benefits**:
- Survives configuration changes (screen rotation)
- Separates UI logic from business logic
- Makes testing easier
- Provides lifecycle awareness
### 3. Repository Layer
**Location**: `data/repository/`
**Responsibilities**:
- Abstract data sources
- Provide clean API to ViewModel
- Handle data operations
- Implement error handling
- Coordinate multiple data sources (if needed)
**Key Components**:
#### MastodonRepository.kt
Provides data operations for Mastodon API.
**Methods**:
- `getPublicTimeline()`: Fetch public timeline
- `getInstanceInfo()`: Get instance information
**Pattern**:
```kotlin
suspend fun getPublicTimeline(limit: Int, local: Boolean): Result<List<Status>> {
return withContext(Dispatchers.IO) {
try {
val response = apiService.getPublicTimeline(limit, local)
if (response.isSuccessful) {
Result.success(response.body() ?: emptyList())
} else {
Result.failure(Exception("Error: ${response.code()}"))
}
} catch (e: Exception) {
Result.failure(e)
}
}
}
```
**Benefits**:
- Single source of truth
- Easy to test with mock data
- Can add caching layer
- Switches between local/remote data
### 4. Data Source Layer
**Location**: `data/api/`, `data/models/`
**Responsibilities**:
- Define API endpoints
- Make network requests
- Parse JSON responses
- Define data models
**Key Components**:
#### MastodonApiService.kt
Retrofit interface defining API endpoints.
```kotlin
interface MastodonApiService {
@GET("api/v1/timelines/public")
suspend fun getPublicTimeline(
@Query("limit") limit: Int = 20,
@Query("local") local: Boolean = false
): Response<List<Status>>
}
```
#### Data Models
**Status.kt**: Represents a post/toot
**Account.kt**: Represents a user account
**MediaAttachment.kt**: Represents media files
**Instance.kt**: Represents server instance info
## Data Flow
### Loading Timeline Example
```
1. User opens app
2. MainActivity creates ViewModel
3. ViewModel initializes and calls loadTimeline()
4. ViewModel sets state to Loading
5. UI shows loading indicator
6. ViewModel calls repository.getPublicTimeline()
7. Repository calls apiService.getPublicTimeline()
8. Retrofit makes HTTP request to Mastodon API
9. API returns JSON response
10. Gson parses JSON to List<Status>
11. Repository returns Result.success(statuses)
12. ViewModel updates state to Success(statuses)
13. UI recomposes with status list
14. StatusCard components render each status
```
### Error Handling Flow
```
Network Error occurs
Repository catches exception
Returns Result.failure(exception)
ViewModel updates state to Error(message)
UI shows error message and retry button
User clicks retry
Flow starts again from step 3
```
## State Management
### UI State Pattern
The app uses **unidirectional data flow** with sealed classes for state:
```kotlin
sealed class TimelineUiState {
object Loading : TimelineUiState()
data class Success(val statuses: List<Status>) : TimelineUiState()
data class Error(val message: String) : TimelineUiState()
}
```
**Benefits**:
- Type-safe state representation
- Exhaustive when expressions
- Clear state transitions
- Easy to add new states
### StateFlow vs LiveData
The app uses **StateFlow** instead of LiveData because:
- Better Kotlin coroutines integration
- More powerful operators
- Lifecycle-aware collection in Compose
- Can be used outside Android
## Dependency Management
### Current Implementation
The app uses **manual dependency injection** in `MainActivity`:
```kotlin
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://mastodon.social/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(MastodonApiService::class.java)
val repository = MastodonRepository(apiService)
val viewModel = TimelineViewModel(repository)
```
### Future Improvements
For larger apps, consider:
- **Hilt**: Dependency injection framework
- **Koin**: Lightweight DI for Kotlin
- **Manual DI with modules**: Create separate DI container classes
## Threading Model
### Coroutines and Dispatchers
The app uses Kotlin coroutines for async operations:
**Dispatchers.Main**: UI operations
```kotlin
viewModelScope.launch { // Main by default
_uiState.value = TimelineUiState.Loading
}
```
**Dispatchers.IO**: Network and disk I/O
```kotlin
withContext(Dispatchers.IO) {
apiService.getPublicTimeline()
}
```
**Dispatcher.Default**: CPU-intensive work
```kotlin
withContext(Dispatchers.Default) {
// Heavy computation
}
```
## Error Handling Strategy
### Result Pattern
The repository uses Kotlin's `Result` type:
```kotlin
suspend fun getPublicTimeline(): Result<List<Status>> {
return try {
Result.success(data)
} catch (e: Exception) {
Result.failure(e)
}
}
```
**Consumption in ViewModel**:
```kotlin
repository.getPublicTimeline().fold(
onSuccess = { data -> /* handle success */ },
onFailure = { error -> /* handle error */ }
)
```
### Error Types
1. **Network Errors**: No internet, timeout
2. **HTTP Errors**: 4xx, 5xx status codes
3. **Parse Errors**: Invalid JSON
4. **Unknown Errors**: Unexpected exceptions
## Testing Strategy
### Unit Testing
**Repository Layer**:
```kotlin
@Test
fun `getPublicTimeline returns success with valid data`() = runTest {
val mockApi = mock<MastodonApiService>()
val repository = MastodonRepository(mockApi)
// Test implementation
}
```
**ViewModel Layer**:
```kotlin
@Test
fun `loadTimeline updates state to Success`() = runTest {
val mockRepository = mock<MastodonRepository>()
val viewModel = TimelineViewModel(mockRepository)
// Test implementation
}
```
### Integration Testing
Test complete flows from ViewModel to Repository to API.
### UI Testing
Use Compose Testing to test UI components:
```kotlin
@Test
fun statusCard_displays_content() {
composeTestRule.setContent {
StatusCard(status = testStatus)
}
composeTestRule
.onNodeWithText("Test content")
.assertIsDisplayed()
}
```
## Performance Considerations
### Image Loading
**Coil** library handles:
- Async loading
- Caching (memory + disk)
- Placeholder/error images
- Automatic lifecycle management
### List Performance
**LazyColumn** provides:
- Lazy loading (only visible items rendered)
- Recycling (item reuse)
- Efficient scrolling
### Memory Management
- ViewModels survive configuration changes
- Images are cached efficiently
- Lists use lazy loading
## Security Considerations
1. **HTTPS Only**: All API calls use HTTPS
2. **No Cleartext Traffic**: Disabled in manifest
3. **Input Validation**: Validate all user input
4. **Rate Limiting**: Respect API rate limits
5. **Error Messages**: Don't expose sensitive info in errors
## Future Enhancements
### Recommended Additions
1. **Authentication**: OAuth 2.0 implementation
2. **Database**: Room for offline caching
3. **Pagination**: Implement infinite scroll
4. **Deep Linking**: Handle Mastodon URLs
5. **Search**: Add search functionality
6. **Notifications**: Push notifications support
7. **Multiple Accounts**: Account switching
8. **Dark Theme**: Complete theme support
9. **Accessibility**: Enhanced accessibility features
10. **Testing**: Comprehensive test coverage
### Architecture Evolution
As the app grows:
- Move to multi-module architecture
- Implement Clean Architecture layers
- Add use cases/interactors
- Separate feature modules
- Add shared UI components module
## References
- [Guide to app architecture (Android)](https://developer.android.com/topic/architecture)
- [ViewModel Overview](https://developer.android.com/topic/libraries/architecture/viewmodel)
- [Repository Pattern](https://developer.android.com/codelabs/basic-android-kotlin-training-repository-pattern)
- [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html)
- [Jetpack Compose Architecture](https://developer.android.com/jetpack/compose/architecture)

153
docs/DOCUMENTATION_INDEX.md Archivo normal
Ver fichero

@@ -0,0 +1,153 @@
# Documentation Index
Welcome to the My ActivityPub documentation! This index will help you find the information you need.
## 📚 Documentation Files
### Getting Started
1. **[README.md](../README.md)** - Project overview, features, and quick start guide
- What is My ActivityPub?
- Features and screenshots
- Tech stack overview
- Quick installation instructions
2. **[SETUP.md](SETUP.md)** - Complete development environment setup
- Prerequisites and required software
- Step-by-step project setup
- Building and running the app
- Troubleshooting common issues
- IDE configuration tips
### Development
3. **[ARCHITECTURE.md](ARCHITECTURE.md)** - Application architecture and design
- MVVM architecture explained
- Layer responsibilities
- Data flow diagrams
- State management patterns
- Threading model with coroutines
- Testing strategies
4. **[API.md](API.md)** - Mastodon API integration documentation
- API endpoints used
- Request/response examples
- Data models explained
- Error handling
- Rate limiting
- Best practices
5. **[CONTRIBUTING.md](../CONTRIBUTING.md)** - How to contribute to the project
- Code of conduct
- Development workflow
- Coding standards
- Commit message guidelines
- Pull request process
- Testing requirements
### Legal
6. **[LICENSE](../LICENSE)** - MIT License
- Copyright information
- Terms and conditions
- Usage rights
## 🎯 Quick Navigation
### I want to...
#### ...understand what this app does
→ Start with [README.md](../README.md)
#### ...set up my development environment
→ Follow [SETUP.md](SETUP.md)
#### ...understand the code structure
→ Read [ARCHITECTURE.md](ARCHITECTURE.md)
#### ...work with the Mastodon API
→ Check [API.md](API.md)
#### ...contribute code
→ Read [CONTRIBUTING.md](../CONTRIBUTING.md)
#### ...build the APK
→ See "Building the Project" in [SETUP.md](SETUP.md)
#### ...fix a bug or add a feature
→ Follow the workflow in [CONTRIBUTING.md](../CONTRIBUTING.md)
## 📖 Reading Order
### For New Developers
1. **README.md** - Get an overview
2. **SETUP.md** - Set up your environment
3. **ARCHITECTURE.md** - Understand the codebase
4. **API.md** - Learn about the API
5. **CONTRIBUTING.md** - Start contributing
### For Contributors
1. **CONTRIBUTING.md** - Understand the process
2. **ARCHITECTURE.md** - Learn the architecture
3. **API.md** - API reference as needed
4. **SETUP.md** - Troubleshooting reference
### For Users
1. **README.md** - Features and installation
2. **LICENSE** - Usage terms
## 📝 Additional Resources
### Code Documentation
All public APIs are documented with KDoc comments:
```kotlin
/**
* Loads the public timeline from the Mastodon instance
*
* @param limit Maximum number of statuses to fetch
* @param local Show only local statuses if true
* @return Result containing list of statuses or error
*/
suspend fun getPublicTimeline(limit: Int, local: Boolean): Result<List<Status>>
```
### External Resources
- **[Android Developers](https://developer.android.com/)** - Official Android documentation
- **[Kotlin Docs](https://kotlinlang.org/docs/home.html)** - Kotlin programming language
- **[Jetpack Compose](https://developer.android.com/jetpack/compose)** - Modern UI toolkit
- **[Mastodon API](https://docs.joinmastodon.org/api/)** - API specification
- **[Material Design 3](https://m3.material.io/)** - Design system
## 🔍 Document Summaries
### README.md
- **Purpose**: Project introduction and quick start
- **Audience**: Everyone
- **Length**: Comprehensive overview
- **Key Sections**: Features, tech stack, building, configuration
### SETUP.md
- **Purpose**: Development environment setup
- **Audience**: Developers setting up for the first time
- **Length**: Detailed step-by-step guide
- **Key Sections**: Prerequisites, setup steps, troubleshooting
### ARCHITECTURE.md
- **Purpose**: Explain code organization and patterns
- **Audience**: Developers who want to understand or modify the codebase
- **Length**: In-depth technical documentation
- **Key Sections**: MVVM layers, data flow, state management
### API.md
- **Purpose**: Document Mastodon API integration
- **Audience**: Developers working with API calls
- **Length**: Comprehensive API reference
- **Key Sections**: Endpoints, data models, error handling
### CONTRIBUTING.md
- **Purpose**: Guide for project contributors
- **Audience**: Anyone who wants to contribute
- **Length**: Complete contribution guide
- **Key Sections**: Coding standards, commit guidelines, PR process
## 📋 Checklist for New Developers
Before starting development, make sure you've:
- [ ] Read the README.md
- [ ] Followed SETUP.md to set up your environment
- [ ] Successfully built and run the app
- [ ] Reviewed ARCHITECTURE.md to understand the code structure
- [ ] Read CONTRIBUTING.md to understand the workflow
- [ ] Familiarized yourself with the API.md for API details
## 🤝 Getting Help
If you can't find what you're looking for:
1. **Search the docs** - Use Ctrl+F to search within documents
2. **Check existing issues** - Someone may have asked already
3. **Read the code** - Code comments provide additional context
4. **Ask in discussions** - Start a conversation
5. **Open an issue** - For bugs or documentation gaps
## 📊 Documentation Status
| Document | Status | Last Updated |
|----------|--------|--------------|
| README.md | ✅ Complete | 2026-01-24 |
| SETUP.md | ✅ Complete | 2026-01-24 |
| ARCHITECTURE.md | ✅ Complete | 2026-01-24 |
| API.md | ✅ Complete | 2026-01-24 |
| CONTRIBUTING.md | ✅ Complete | 2026-01-24 |
| LICENSE | ✅ Complete | 2026-01-24 |
## 🔄 Keeping Documentation Updated
Documentation should be updated when:
- New features are added
- Architecture changes
- API integration changes
- Build process changes
- New dependencies are added
Contributors: Please update relevant docs with your changes!
---
**Note**: All documentation is written in English to ensure accessibility for the global developer community.
**Tip**: You can use tools like [Grip](https://github.com/joeyespo/grip) to preview Markdown files locally with GitHub styling.

364
docs/OAUTH_LOGIN.md Archivo normal
Ver fichero

@@ -0,0 +1,364 @@
# OAuth Login and Authentication
This document explains the OAuth authentication implementation in My ActivityPub app.
## Overview
The app supports two modes:
1. **Guest Mode** (Default): Browse public federated timeline without authentication
2. **Authenticated Mode**: Login with OAuth to access your home timeline and instance
## Features
### Guest Mode
- Opens directly to the public federated timeline from `mastodon.social`
- No authentication required
- Browse posts from the entire fediverse
- Option to login at any time
### Authenticated Mode
When logged in, users can:
- Access their home timeline (posts from followed accounts)
- Switch between Public and Home timelines
- See their username in the app header
- Logout when desired
- Persistent login (saved across app restarts)
## OAuth Flow
### 1. App Registration
When a user initiates login:
1. User enters their instance domain (e.g., `mastodon.online`)
2. App registers itself with the instance via `/api/v1/apps`
3. Receives `client_id` and `client_secret`
4. Credentials are saved securely in DataStore
### 2. Authorization
1. App constructs authorization URL:
```
https://{instance}/oauth/authorize?
client_id={client_id}&
scope=read write follow&
redirect_uri=myactivitypub://oauth&
response_type=code
```
2. Opens URL in Custom Chrome Tab (in-app browser)
3. User authorizes the app on their instance
4. Instance redirects to: `myactivitypub://oauth?code={auth_code}`
5. App catches the deep link and extracts the code
### 3. Token Exchange
1. App exchanges authorization code for access token
2. POST to `/oauth/token` with:
- `client_id`
- `client_secret`
- `code` (from authorization)
- `grant_type=authorization_code`
3. Receives `access_token`
4. Token is saved securely in DataStore
### 4. Authenticated Requests
All subsequent API requests include:
```
Authorization: Bearer {access_token}
```
## Architecture
### Components
#### AuthRepository
**Location**: `data/repository/AuthRepository.kt`
Handles all authentication operations:
- `registerApp()`: Register OAuth app with instance
- `getAuthorizationUrl()`: Generate OAuth URL
- `obtainToken()`: Exchange code for token
- `verifyCredentials()`: Verify user account
- `logout()`: Clear stored credentials
- `userSession`: Flow of current session state
#### AuthViewModel
**Location**: `ui/viewmodel/AuthViewModel.kt`
Manages authentication state:
```kotlin
sealed class AuthState {
object Loading
object NotAuthenticated
object LoggingIn
data class NeedsAuthorization(val authUrl: String)
object LoginComplete
data class Authenticated(val session: UserSession)
data class Error(val message: String)
}
```
#### LoginScreen
**Location**: `ui/screens/LoginScreen.kt`
UI for login:
- Instance domain input
- Login button
- Continue as guest option
- Error display
### Data Models
#### UserSession
```kotlin
data class UserSession(
val instance: String, // e.g., "mastodon.social"
val accessToken: String, // OAuth access token
val account: Account? // User account info
)
```
#### AppRegistration
```kotlin
data class AppRegistration(
val id: String,
val name: String,
val clientId: String,
val clientSecret: String,
val redirectUri: String
)
```
#### TokenResponse
```kotlin
data class TokenResponse(
val accessToken: String,
val tokenType: String, // Usually "Bearer"
val scope: String, // "read write follow"
val createdAt: Long
)
```
## Security
### Token Storage
- Tokens stored using **DataStore Preferences**
- Encrypted at rest by Android system
- Not accessible to other apps
- Cleared on logout
### OAuth Best Practices
✅ **Implemented**:
- Authorization Code Flow (most secure)
- Custom Chrome Tabs (prevents phishing)
- HTTPS only
- State parameter handling
- Token refresh (TODO)
❌ **Not Implemented** (Future):
- PKCE (Proof Key for Code Exchange)
- Token refresh
- Token expiration handling
- Revocation on logout
## API Endpoints
### Register App
```
POST /api/v1/apps
```
**Request**:
```
client_name=My ActivityPub
redirect_uris=myactivitypub://oauth
scopes=read write follow
```
**Response**:
```json
{
"id": "123",
"name": "My ActivityPub",
"client_id": "...",
"client_secret": "...",
"redirect_uri": "myactivitypub://oauth"
}
```
### Obtain Token
```
POST /oauth/token
```
**Request**:
```
client_id={client_id}
client_secret={client_secret}
redirect_uri=myactivitypub://oauth
grant_type=authorization_code
code={authorization_code}
scope=read write follow
```
**Response**:
```json
{
"access_token": "...",
"token_type": "Bearer",
"scope": "read write follow",
"created_at": 1234567890
}
```
### Verify Credentials
```
GET /api/v1/accounts/verify_credentials
Authorization: Bearer {access_token}
```
**Response**:
```json
{
"id": "123",
"username": "alice",
"display_name": "Alice",
"avatar": "https://...",
...
}
```
## Configuration
### OAuth Scopes
Currently requested scopes:
- `read`: Read user data
- `write`: Post statuses, follow accounts
- `follow`: Follow/unfollow accounts
### Redirect URI
Fixed redirect URI:
```
myactivitypub://oauth
```
Registered in `AndroidManifest.xml`:
```xml
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myactivitypub"
android:host="oauth" />
</intent-filter>
```
## Usage Examples
### Login Flow (User Perspective)
1. **Open app**
- See public timeline (guest mode)
2. **Tap menu (⋮) → Login**
- Enter instance: `mastodon.social`
- Tap "Login" button
3. **Authorize in browser**
- Browser opens showing Mastodon login
- Enter credentials (if not logged in)
- Click "Authorize"
4. **Return to app**
- Automatically redirects back
- Shows "Loading..."
- Displays home timeline
5. **Use app**
- See home timeline (followed accounts)
- Tap menu → Switch to Public
- Tap menu → Logout (when done)
### Programmatic Usage
```kotlin
// In a Composable
val authViewModel: AuthViewModel = viewModel()
val userSession by authViewModel.userSession.collectAsState()
// Start login
authViewModel.startLogin("mastodon.social")
// Complete login (after OAuth callback)
authViewModel.completeLogin(authorizationCode)
// Logout
authViewModel.logout()
// Check if logged in
if (userSession != null) {
// User is authenticated
val username = userSession.account?.username
}
```
## Troubleshooting
### Common Issues
#### Login button doesn't work
**Symptoms**: Nothing happens when tapping Login
**Causes**:
- Invalid instance domain
- Network connectivity issues
- Instance doesn't support OAuth
**Solutions**:
1. Check internet connection
2. Verify instance domain (no `https://`, just domain)
3. Try a known instance like `mastodon.social`
#### Browser doesn't open
**Symptoms**: No browser appears after tapping Login
**Causes**:
- Chrome not installed
- Intent filter not configured
**Solutions**:
1. Install Chrome or another browser
2. Check `AndroidManifest.xml` has intent filter
#### Can't return to app after authorization
**Symptoms**: Stuck in browser after authorizing
**Causes**:
- Deep link not registered
- Activity launch mode incorrect
**Solutions**:
1. Verify `launchMode="singleTask"` in manifest
2. Check intent filter configuration
3. Manually return to app
#### Token errors
**Symptoms**: "Failed to obtain token" error
**Causes**:
- Invalid authorization code
- Expired code
- Network issues
**Solutions**:
1. Try logging in again
2. Check network connection
3. Clear app data and retry
### Debug Mode
To enable detailed logging:
```kotlin
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY // Change from BASIC
}
```
View logs:
```bash
adb logcat | grep -E "(OAuth|Auth|Token)"
```
## Future Enhancements
### Planned Features
- [ ] Token refresh mechanism
- [ ] PKCE support
- [ ] Multiple account support
- [ ] Account switching
- [ ] Remember last used instance
- [ ] Instance suggestions/autocomplete
- [ ] Token revocation on logout
- [ ] Offline mode with cached data
- [ ] Biometric authentication
- [ ] Session timeout
### Security Improvements
- [ ] Implement PKCE
- [ ] Add state parameter validation
- [ ] Token rotation
- [ ] Secure key storage (Android Keystore)
- [ ] Certificate pinning
## Testing
### Manual Testing
1. **Guest Mode**:
- Open app → Should show public timeline
- No login required
2. **Login Flow**:
- Tap menu → Login
- Enter `mastodon.social`
- Should open browser
- Authorize app
- Should return to app automatically
- Should show home timeline
3. **Timeline Switching**:
- Login first
- Tap menu → Switch to Public
- Should show public timeline
- Tap menu → Switch to Home
- Should show home timeline
4. **Logout**:
- While logged in
- Tap menu → Logout
- Should return to public timeline (guest mode)
### Automated Testing
```kotlin
@Test
fun authRepository_registerApp_success() = runTest {
val result = authRepository.registerApp("mastodon.social")
assertTrue(result.isSuccess)
}
@Test
fun authViewModel_login_updatesState() = runTest {
authViewModel.startLogin("mastodon.social")
// Verify state changes to LoggingIn, then NeedsAuthorization
}
```
## References
- [Mastodon OAuth Documentation](https://docs.joinmastodon.org/spec/oauth/)
- [RFC 6749 - OAuth 2.0](https://tools.ietf.org/html/rfc6749)
- [Android Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs/)
- [DataStore Documentation](https://developer.android.com/topic/libraries/architecture/datastore)
## Support
For issues related to authentication:
1. Check this documentation
2. Review the code in `AuthRepository.kt`
3. Check LogCat for errors
4. Open an issue on GitHub with:
- Instance you're trying to connect to
- Error messages
- Steps to reproduce

248
docs/SETUP.md Archivo normal
Ver fichero

@@ -0,0 +1,248 @@
# Development Setup Guide
This guide will help you set up the My ActivityPub project for development.
## Prerequisites
### Required Software
1. **Android Studio**
- Version: Hedgehog (2023.1.1) or newer
- Download: https://developer.android.com/studio
2. **Java Development Kit (JDK)**
- Version: JDK 11 or higher
- Android Studio includes a JDK, or download from: https://adoptium.net/
3. **Git**
- Version: Latest stable
- Download: https://git-scm.com/downloads
4. **Android SDK**
- API Level 24 (Android 7.0) minimum
- API Level 35 (Android 14) target
- Installed via Android Studio SDK Manager
### Recommended Tools
- **Android Device** or **Emulator** for testing
- **ADB (Android Debug Bridge)** - included with Android Studio
- **Gradle** - wrapper included in project
## Project Setup
### 1. Clone the Repository
```bash
git clone https://github.com/your-username/MyActivityPub.git
cd MyActivityPub
```
### 2. Open in Android Studio
1. Launch Android Studio
2. Select **File > Open**
3. Navigate to the cloned repository
4. Click **OK**
### 3. Gradle Sync
Android Studio will automatically trigger a Gradle sync. If not:
1. Click **File > Sync Project with Gradle Files**
2. Wait for sync to complete
3. Resolve any errors if they appear
### 4. Install Dependencies
The Gradle build system will automatically download all dependencies:
- Kotlin standard library
- Jetpack Compose libraries
- Retrofit for networking
- Coil for image loading
- Material 3 components
### 5. Configure Build Variants
1. Click **Build > Select Build Variant**
2. Choose `debug` for development
3. Use `release` for production builds
## Building the Project
### Debug Build
```bash
# From command line
./gradlew assembleDebug
# Output location
app/build/outputs/apk/debug/app-debug.apk
```
### Release Build
```bash
# Create signed release APK
./gradlew assembleRelease
# Output location
app/build/outputs/apk/release/app-release.apk
```
## Running the App
### On Physical Device
1. Enable **Developer Options** on your Android device:
- Go to **Settings > About Phone**
- Tap **Build Number** 7 times
2. Enable **USB Debugging**:
- Go to **Settings > Developer Options**
- Enable **USB Debugging**
3. Connect device via USB
4. In Android Studio:
- Click **Run > Run 'app'**
- Or press **Shift + F10**
- Select your device
### On Emulator
1. Create an AVD (Android Virtual Device):
- Click **Tools > Device Manager**
- Click **Create Device**
- Select a device definition (e.g., Pixel 6)
- Select system image (API 24+)
- Click **Finish**
2. Run the app:
- Click **Run > Run 'app'**
- Select the emulator
## Configuration
### Gradle Properties
Edit `gradle.properties` to configure build settings:
```properties
# Increase memory for large projects
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
# Enable parallel builds
org.gradle.parallel=true
# Enable configuration cache (Gradle 8.0+)
org.gradle.configuration-cache=true
```
### API Configuration
To connect to a different Mastodon instance, edit `MainActivity.kt`:
```kotlin
val retrofit = Retrofit.Builder()
.baseUrl("https://your-instance.social/") // Change this
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
```
## Troubleshooting
### Common Issues
#### 1. Gradle Sync Failed
**Problem**: "Could not download dependencies"
**Solutions**:
```bash
# Clear Gradle cache
rm -rf ~/.gradle/caches/
./gradlew clean --no-daemon
# Or in Android Studio:
# File > Invalidate Caches > Invalidate and Restart
```
#### 2. Build Failed with Memory Error
**Problem**: "Java heap space" or "OutOfMemoryError"
**Solution**: Increase memory in `gradle.properties`:
```properties
org.gradle.jvmargs=-Xmx4096m
```
#### 3. SDK Not Found
**Problem**: "Failed to find target with hash string 'android-35'"
**Solution**:
1. Open SDK Manager: **Tools > SDK Manager**
2. Install Android 14.0 (API 35)
3. Sync Gradle files
#### 4. Emulator Won't Start
**Problem**: Emulator crashes or doesn't start
**Solutions**:
- Enable virtualization in BIOS/UEFI
- Install HAXM (Intel) or WHPX (Windows)
- Reduce emulator RAM allocation
- Use ARM system image instead of x86
#### 5. App Crashes on Launch
**Problem**: App crashes immediately after launch
**Solutions**:
- Check Logcat for error messages
- Verify internet permission in manifest
- Clear app data: `adb shell pm clear com.manalejandro.myactivitypub`
### Debugging
#### View Logs
```bash
# View all logs
adb logcat
# Filter by app
adb logcat | grep MyActivityPub
# Filter by tag
adb logcat -s TimelineViewModel
# Clear logs
adb logcat -c
```
#### Debug in Android Studio
1. Set breakpoints in code
2. Click **Run > Debug 'app'**
3. Interact with app
4. Use debug panel to inspect variables
## IDE Configuration
### Android Studio Settings
Recommended settings for development:
1. **Code Style**:
- **Settings > Editor > Code Style > Kotlin**
- Set from: **Kotlin style guide**
2. **Auto Import**:
- **Settings > Editor > General > Auto Import**
- Enable **Add unambiguous imports on the fly**
- Enable **Optimize imports on the fly**
3. **Live Templates**:
- **Settings > Editor > Live Templates**
- Useful templates: `comp`, `vm`, `repo`
4. **Version Control**:
- **Settings > Version Control > Git**
- Configure your Git author info
### Useful Plugins
- **Rainbow Brackets**: Colorize bracket pairs
- **GitToolBox**: Enhanced Git integration
- **Key Promoter X**: Learn keyboard shortcuts
- **ADB Idea**: Quick ADB commands
- **.ignore**: Manage .gitignore files
## Testing Setup
### Unit Tests
```bash
# Run all unit tests
./gradlew test
# Run tests for specific variant
./gradlew testDebugUnitTest
```
### Instrumented Tests
```bash
# Run on connected device/emulator
./gradlew connectedAndroidTest
```
### Test Coverage
```bash
# Generate coverage report
./gradlew jacocoTestReport
# View report at:
# app/build/reports/jacoco/test/html/index.html
```
## Code Quality
### Lint Checks
```bash
# Run lint
./gradlew lint
# View report at:
# app/build/reports/lint-results.html
```
### Static Analysis
```bash
# Run detekt (if configured)
./gradlew detekt
```
## Environment Variables
For sensitive data, use local environment variables:
```bash
# In ~/.bashrc or ~/.zshrc
export MASTODON_BASE_URL="https://mastodon.social/"
export API_KEY="your-api-key"
```
Access in Gradle:
```kotlin
android {
defaultConfig {
buildConfigField("String", "BASE_URL", "\"${System.getenv("MASTODON_BASE_URL")}\"")
}
}
```
## Next Steps
After setup:
1. Read [ARCHITECTURE.md](ARCHITECTURE.md) to understand the codebase
2. Check [CONTRIBUTING.md](../CONTRIBUTING.md) for contribution guidelines
3. Review [API.md](API.md) for API documentation
4. Start coding! 🚀
## Getting Help
If you encounter issues:
1. Check this guide thoroughly
2. Search existing [Issues](https://github.com/your-repo/issues)
3. Check [Stack Overflow](https://stackoverflow.com/questions/tagged/android)
4. Ask in [Discussions](https://github.com/your-repo/discussions)
5. Create a new issue with details
## Additional Resources
- [Android Developer Guides](https://developer.android.com/guide)
- [Kotlin Documentation](https://kotlinlang.org/docs/home.html)
- [Jetpack Compose Pathway](https://developer.android.com/courses/pathways/compose)
- [Mastodon API Docs](https://docs.joinmastodon.org/api/)