337
docs/API.md
Archivo normal
337
docs/API.md
Archivo normal
@@ -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
476
docs/ARCHITECTURE.md
Archivo normal
@@ -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
153
docs/DOCUMENTATION_INDEX.md
Archivo normal
@@ -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
364
docs/OAUTH_LOGIN.md
Archivo normal
@@ -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
248
docs/SETUP.md
Archivo normal
@@ -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/)
|
||||
Referencia en una nueva incidencia
Block a user