Compare commits
15 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05a768fbf6 | ||
|
|
e9ee7ea61b | ||
|
|
c397180786 | ||
|
|
a2f1033184 | ||
|
|
fb67792cf1 | ||
|
|
089d3b3db6 | ||
|
|
5740fa2748 | ||
|
|
dff40712c5 | ||
|
|
46cd22b2e4 | ||
|
|
0687d1a7d7 | ||
|
|
c66d30af19 | ||
|
|
5d81c247aa | ||
|
|
5ab1be6cd4 | ||
|
|
5b6b7438df | ||
|
|
4a081300cb |
73 changed files with 5312 additions and 600 deletions
BIN
.claude/.DS_Store
vendored
Normal file
BIN
.claude/.DS_Store
vendored
Normal file
Binary file not shown.
15
.claude/commands/commit-push-pr.md
Normal file
15
.claude/commands/commit-push-pr.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
|
||||||
|
description: Create a git commit
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Current git status: !`git status`
|
||||||
|
- Current git diff (staged and unstaged changes): !`git diff HEAD`
|
||||||
|
- Current branch: !`git branch --show-current`
|
||||||
|
- Recent commits: !`git log --oneline -10`
|
||||||
|
|
||||||
|
## Your task
|
||||||
|
|
||||||
|
Based on the above changes, create a single git commit. Then push it.
|
||||||
15
.claude/commands/new-version.md
Normal file
15
.claude/commands/new-version.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
|
||||||
|
description: Do migration tests first. Then: update changelog, update readme, update version in gradle files, create a git commit and push it
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- Current git status: !`git status`
|
||||||
|
- Current git diff (staged and unstaged changes): !`git diff HEAD`
|
||||||
|
- Current branch: !`git branch --show-current`
|
||||||
|
- Recent commits: !`git log --oneline -10`
|
||||||
|
|
||||||
|
## Your task
|
||||||
|
|
||||||
|
Do migration tests first. Then: update changelog, update readme, update version in gradle files, create a git commit and push it
|
||||||
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
name: Bug Report
|
||||||
|
about: Report a bug to help us improve FastMask
|
||||||
|
title: '[Bug] '
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## Describe the Bug
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
1. Go to '...'
|
||||||
|
2. Tap on '...'
|
||||||
|
3. Scroll to '...'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
What you expected to happen.
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
What actually happened.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
- **Device**: [e.g., Pixel 7, Samsung Galaxy S23]
|
||||||
|
- **Android Version**: [e.g., Android 14]
|
||||||
|
- **App Version**: [e.g., 1.0.0]
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
Add any other context about the problem here.
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Documentation
|
||||||
|
url: https://github.com/pawelorzech/FastMask#readme
|
||||||
|
about: Check the README for setup and usage instructions
|
||||||
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
name: Feature Request
|
||||||
|
about: Suggest a new feature for FastMask
|
||||||
|
title: '[Feature] '
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
A clear description of the problem or need. Ex. "I'm always frustrated when..."
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
Describe the solution you'd like.
|
||||||
|
|
||||||
|
## Alternatives Considered
|
||||||
|
Any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
## Use Case
|
||||||
|
Describe how you would use this feature.
|
||||||
|
|
||||||
|
## Mockups / Examples
|
||||||
|
If applicable, add mockups, sketches, or examples from other apps.
|
||||||
|
|
||||||
|
## Additional Context
|
||||||
|
Add any other context about the feature request here.
|
||||||
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
## Description
|
||||||
|
Brief description of the changes in this PR.
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||||
|
- [ ] New feature (non-breaking change that adds functionality)
|
||||||
|
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||||
|
- [ ] Documentation update
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
Fixes #(issue number)
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
- Change 1
|
||||||
|
- Change 2
|
||||||
|
- Change 3
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
Describe the tests you ran to verify your changes:
|
||||||
|
- [ ] Tested on emulator (API level: )
|
||||||
|
- [ ] Tested on physical device (model: )
|
||||||
|
- [ ] Unit tests pass
|
||||||
|
- [ ] UI looks correct in light and dark mode
|
||||||
|
|
||||||
|
## Screenshots (if applicable)
|
||||||
|
| Before | After |
|
||||||
|
|--------|-------|
|
||||||
|
| | |
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
- [ ] My code follows the project's style guidelines
|
||||||
|
- [ ] I have performed a self-review of my code
|
||||||
|
- [ ] I have commented my code where necessary
|
||||||
|
- [ ] I have updated the documentation if needed
|
||||||
|
- [ ] My changes generate no new warnings
|
||||||
|
- [ ] Any dependent changes have been merged and published
|
||||||
44
.github/workflows/claude-code-review.yml
vendored
Normal file
44
.github/workflows/claude-code-review.yml
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
name: Claude Code Review
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, ready_for_review, reopened]
|
||||||
|
# Optional: Only run on specific file changes
|
||||||
|
# paths:
|
||||||
|
# - "src/**/*.ts"
|
||||||
|
# - "src/**/*.tsx"
|
||||||
|
# - "src/**/*.js"
|
||||||
|
# - "src/**/*.jsx"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude-review:
|
||||||
|
# Optional: Filter by PR author
|
||||||
|
# if: |
|
||||||
|
# github.event.pull_request.user.login == 'external-contributor' ||
|
||||||
|
# github.event.pull_request.user.login == 'new-developer' ||
|
||||||
|
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run Claude Code Review
|
||||||
|
id: claude-review
|
||||||
|
uses: anthropics/claude-code-action@v1
|
||||||
|
with:
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
|
||||||
|
plugins: 'code-review@claude-code-plugins'
|
||||||
|
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
|
||||||
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||||
|
|
||||||
50
.github/workflows/claude.yml
vendored
Normal file
50
.github/workflows/claude.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
name: Claude Code
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
pull_request_review_comment:
|
||||||
|
types: [created]
|
||||||
|
issues:
|
||||||
|
types: [opened, assigned]
|
||||||
|
pull_request_review:
|
||||||
|
types: [submitted]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
claude:
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||||
|
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||||
|
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: read
|
||||||
|
issues: read
|
||||||
|
id-token: write
|
||||||
|
actions: read # Required for Claude to read CI results on PRs
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Run Claude Code
|
||||||
|
id: claude
|
||||||
|
uses: anthropics/claude-code-action@v1
|
||||||
|
with:
|
||||||
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||||
|
|
||||||
|
# This is an optional setting that allows Claude to read CI results on PRs
|
||||||
|
additional_permissions: |
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||||
|
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||||
|
|
||||||
|
# Optional: Add claude_args to customize behavior and configuration
|
||||||
|
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||||
|
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||||
|
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="21" />
|
<bytecodeTargetLevel target="17" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
|
|
||||||
26
AGENTS.md
Normal file
26
AGENTS.md
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Build & Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build debug APK
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# Build release APK (with ProGuard/R8 minification)
|
||||||
|
./gradlew assembleRelease
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
|
# Run instrumented tests (requires emulator/device)
|
||||||
|
./gradlew connectedAndroidTest
|
||||||
|
|
||||||
|
# Clean build
|
||||||
|
./gradlew clean
|
||||||
|
```
|
||||||
|
|
||||||
|
APK outputs: `app/build/outputs/apk/`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
TODO: Document how to run the app from Android Studio/emulator once confirmed.
|
||||||
84
CLAUDE.md
Normal file
84
CLAUDE.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build debug APK
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# Build release APK (with ProGuard/R8 minification)
|
||||||
|
./gradlew assembleRelease
|
||||||
|
|
||||||
|
# Run unit tests
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
|
# Run instrumented tests (requires emulator/device)
|
||||||
|
./gradlew connectedAndroidTest
|
||||||
|
|
||||||
|
# Clean build
|
||||||
|
./gradlew clean
|
||||||
|
```
|
||||||
|
|
||||||
|
APK outputs: `app/build/outputs/apk/`
|
||||||
|
|
||||||
|
## Project Configuration
|
||||||
|
|
||||||
|
- **SDK**: Compile/Target 34, Min 26
|
||||||
|
- **JDK**: 17
|
||||||
|
- **Kotlin**: 1.9.22
|
||||||
|
- **Package**: `com.fastmask`
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Clean Architecture with MVVM pattern. Three distinct layers:
|
||||||
|
|
||||||
|
### Data Layer (`data/`)
|
||||||
|
- `api/` - JMAP protocol integration (Fastmail's native API)
|
||||||
|
- `JmapApi.kt` - API client with session caching
|
||||||
|
- `JmapService.kt` - Retrofit service interface
|
||||||
|
- `JmapModels.kt` - Kotlinx Serialization models
|
||||||
|
- `local/` - Persistence
|
||||||
|
- `TokenStorage.kt` - EncryptedSharedPreferences (lazy-initialized for Hilt compatibility)
|
||||||
|
- `SettingsDataStore.kt` - DataStore for language preferences
|
||||||
|
- `repository/` - Repository implementations
|
||||||
|
|
||||||
|
### Domain Layer (`domain/`)
|
||||||
|
- `model/` - Domain models (`MaskedEmail`, `Language`, `EmailState` enum)
|
||||||
|
- `repository/` - Abstract interfaces
|
||||||
|
- `usecase/` - Business logic (Login, Logout, CRUD for masked emails, language settings)
|
||||||
|
|
||||||
|
### UI Layer (`ui/`)
|
||||||
|
- `auth/`, `list/`, `create/`, `detail/`, `settings/` - Feature screens with ViewModels
|
||||||
|
- `components/` - Reusable composables (MaskedEmailCard, ShimmerEffect, ErrorMessage)
|
||||||
|
- `navigation/` - Jetpack Navigation with shared element transitions
|
||||||
|
- `theme/` - Material 3 theming with dynamic colors
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
**State Management**: `StateFlow<UiState>` for reactive state, `SharedFlow<Event>` for one-time events (navigation, logout)
|
||||||
|
|
||||||
|
**Dependency Injection**: Hilt with `NetworkModule` (Retrofit, OkHttp, Json) and `RepositoryModule` (repository bindings)
|
||||||
|
|
||||||
|
**API Protocol**: JMAP (JSON Mail Access Protocol) with Bearer token auth. Session (accountId, apiUrl) is cached after first call.
|
||||||
|
|
||||||
|
**Security**: Tokens stored in EncryptedSharedPreferences using Android Security Crypto library. TokenStorage uses lazy initialization to prevent Hilt injection issues.
|
||||||
|
|
||||||
|
## Localization
|
||||||
|
|
||||||
|
20 languages supported. String resources in `res/values-*/strings.xml`. In-app language override uses AppCompatDelegate for runtime switching without restart.
|
||||||
|
|
||||||
|
## ProGuard/R8
|
||||||
|
|
||||||
|
Release builds use minification. Key rules in `app/proguard-rules.pro`:
|
||||||
|
- Keep Kotlinx Serialization and JMAP models
|
||||||
|
- Keep Google Tink classes (security-crypto dependency)
|
||||||
|
- Retrofit and OkHttp configurations
|
||||||
|
|
||||||
|
## Commit Message Format
|
||||||
|
|
||||||
|
- `Add: new feature description`
|
||||||
|
- `Fix: bug description`
|
||||||
|
- `Update: what was changed`
|
||||||
|
- `Refactor: what was refactored`
|
||||||
124
CONTRIBUTING.md
Normal file
124
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Contributing to FastMask
|
||||||
|
|
||||||
|
First off, thank you for considering contributing to FastMask! It's people like you that make FastMask a great tool for managing Fastmail masked emails.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
By participating in this project, you agree to maintain a respectful and inclusive environment for everyone.
|
||||||
|
|
||||||
|
## How Can I Contribute?
|
||||||
|
|
||||||
|
### Reporting Bugs
|
||||||
|
|
||||||
|
Before creating bug reports, please check existing issues to avoid duplicates. When you create a bug report, include as many details as possible:
|
||||||
|
|
||||||
|
- **Use a clear and descriptive title**
|
||||||
|
- **Describe the exact steps to reproduce the problem**
|
||||||
|
- **Describe the behavior you observed and what you expected**
|
||||||
|
- **Include your Android version and device model**
|
||||||
|
- **Include screenshots if applicable**
|
||||||
|
|
||||||
|
### Suggesting Features
|
||||||
|
|
||||||
|
Feature suggestions are welcome! Please:
|
||||||
|
|
||||||
|
- **Use a clear and descriptive title**
|
||||||
|
- **Provide a detailed description of the proposed feature**
|
||||||
|
- **Explain why this feature would be useful**
|
||||||
|
- **Include mockups or examples if possible**
|
||||||
|
|
||||||
|
### Pull Requests
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch from `main`:
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
```
|
||||||
|
3. Make your changes
|
||||||
|
4. Test your changes thoroughly
|
||||||
|
5. Commit with a meaningful message:
|
||||||
|
```bash
|
||||||
|
git commit -m "Add: description of your changes"
|
||||||
|
```
|
||||||
|
6. Push to your fork:
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Android Studio](https://developer.android.com/studio) (latest stable version)
|
||||||
|
- JDK 17 or higher
|
||||||
|
- Android SDK with API 34
|
||||||
|
|
||||||
|
### Building the Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone your fork
|
||||||
|
git clone https://github.com/YOUR_USERNAME/FastMask.git
|
||||||
|
cd FastMask
|
||||||
|
|
||||||
|
# Open in Android Studio or build via command line
|
||||||
|
./gradlew assembleDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run unit tests
|
||||||
|
./gradlew test
|
||||||
|
|
||||||
|
# Run instrumented tests (requires emulator or device)
|
||||||
|
./gradlew connectedAndroidTest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### Kotlin
|
||||||
|
|
||||||
|
- Follow [Kotlin coding conventions](https://kotlinlang.org/docs/coding-conventions.html)
|
||||||
|
- Use meaningful names for variables, functions, and classes
|
||||||
|
- Keep functions small and focused
|
||||||
|
- Use Kotlin idioms (scope functions, extension functions, etc.)
|
||||||
|
|
||||||
|
### Compose
|
||||||
|
|
||||||
|
- Keep composables small and reusable
|
||||||
|
- Use `remember` and `derivedStateOf` appropriately
|
||||||
|
- Follow the [Compose API guidelines](https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-api-guidelines.md)
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
- Follow Clean Architecture principles
|
||||||
|
- Keep the data, domain, and UI layers separate
|
||||||
|
- Use use cases for business logic
|
||||||
|
- ViewModels should only contain UI state logic
|
||||||
|
|
||||||
|
### Commit Messages
|
||||||
|
|
||||||
|
Use clear, descriptive commit messages:
|
||||||
|
|
||||||
|
- `Add: new feature description`
|
||||||
|
- `Fix: bug description`
|
||||||
|
- `Update: what was changed`
|
||||||
|
- `Refactor: what was refactored`
|
||||||
|
- `Docs: documentation changes`
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
app/src/main/java/com/fastmask/
|
||||||
|
├── data/ # Data layer (API, storage, repository implementations)
|
||||||
|
├── domain/ # Domain layer (models, repository interfaces, use cases)
|
||||||
|
├── di/ # Hilt dependency injection modules
|
||||||
|
└── ui/ # UI layer (screens, viewmodels, components, theme)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
Feel free to open an issue with the `question` label if you have any questions about contributing.
|
||||||
|
|
||||||
|
Thank you for contributing!
|
||||||
267
README.md
267
README.md
|
|
@ -1,96 +1,247 @@
|
||||||
# FastMask
|
<p align="center">
|
||||||
|
<img src="app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp" width="100" alt="FastMask Logo">
|
||||||
|
</p>
|
||||||
|
|
||||||
A native Android app for managing Fastmail masked emails. Create, view, edit, and manage your masked email addresses directly from your Android device.
|
<h1 align="center">FastMask</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Native Android app for managing Fastmail masked emails</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/pawelorzech/FastMask/releases/latest">
|
||||||
|
<img src="https://img.shields.io/github/v/release/pawelorzech/FastMask?style=flat-square" alt="Latest Release">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/pawelorzech/FastMask/blob/main/LICENSE">
|
||||||
|
<img src="https://img.shields.io/github/license/pawelorzech/FastMask?style=flat-square" alt="License">
|
||||||
|
</a>
|
||||||
|
<a href="https://developer.android.com/about/versions/oreo">
|
||||||
|
<img src="https://img.shields.io/badge/API-26%2B-brightgreen?style=flat-square" alt="API 26+">
|
||||||
|
</a>
|
||||||
|
<a href="https://kotlinlang.org">
|
||||||
|
<img src="https://img.shields.io/badge/Kotlin-100%25-7F52FF?style=flat-square&logo=kotlin&logoColor=white" alt="Kotlin">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#features">Features</a> •
|
||||||
|
<a href="#screenshots">Screenshots</a> •
|
||||||
|
<a href="#installation">Installation</a> •
|
||||||
|
<a href="#setup">Setup</a> •
|
||||||
|
<a href="#tech-stack">Tech Stack</a> •
|
||||||
|
<a href="#architecture">Architecture</a> •
|
||||||
|
<a href="#contributing">Contributing</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
FastMask is a native Android application that lets you manage your [Fastmail](https://www.fastmail.com) masked email addresses directly from your phone. Masked emails are disposable addresses that forward to your real inbox, helping you protect your privacy and reduce spam.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **View Masked Emails** - Browse all your Fastmail masked email addresses in a clean, organized list
|
| Feature | Description |
|
||||||
- **Create New Masks** - Generate new masked email addresses with optional descriptions and domain associations
|
|---------|-------------|
|
||||||
- **Enable/Disable** - Toggle masked emails on or off without deleting them
|
| **View All Masks** | Browse your masked emails in a clean, searchable list |
|
||||||
- **Edit Details** - Update description, associated domain, and URL for any masked email
|
| **Create New** | Generate new masked addresses with custom descriptions |
|
||||||
- **Copy to Clipboard** - Quickly copy email addresses with one tap
|
| **Enable/Disable** | Toggle masks on or off without deleting them |
|
||||||
- **Delete** - Remove masked emails you no longer need
|
| **Edit Details** | Update description, domain, and URL associations |
|
||||||
- **Search** - Filter your masked emails to find what you need
|
| **Quick Copy** | One-tap copy to clipboard |
|
||||||
- **Material You** - Modern Material 3 design with dynamic theming support
|
| **Delete** | Remove masks you no longer need |
|
||||||
|
| **Search & Filter** | Find specific masks instantly |
|
||||||
|
| **Material You** | Dynamic theming that adapts to your wallpaper |
|
||||||
|
| **20 Languages** | Full localization with in-app language picker |
|
||||||
|
| **Settings** | Language selection, contact/feedback, and logout |
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
*Coming soon*
|
<p align="center">
|
||||||
|
<i>Screenshots coming soon</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/screenshots/list.png" width="200" alt="Email List">
|
||||||
|
<img src="docs/screenshots/create.png" width="200" alt="Create Email">
|
||||||
|
<img src="docs/screenshots/detail.png" width="200" alt="Email Detail">
|
||||||
|
</p>
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Download APK
|
||||||
|
|
||||||
|
1. Go to the [Releases](https://github.com/pawelorzech/FastMask/releases/latest) page
|
||||||
|
2. Download the latest APK file
|
||||||
|
3. Enable "Install from unknown sources" if prompted
|
||||||
|
4. Install the APK
|
||||||
|
|
||||||
|
### Build from Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://github.com/pawelorzech/FastMask.git
|
||||||
|
cd FastMask
|
||||||
|
|
||||||
|
# Build debug APK
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# Or build release APK
|
||||||
|
./gradlew assembleRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
The APK will be generated in `app/build/outputs/apk/`
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Android 8.0 (API 26) or higher
|
- Android 8.0 (API 26) or higher
|
||||||
- Fastmail account with API access
|
- Fastmail account with API access
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### From GitHub Releases
|
|
||||||
|
|
||||||
1. Download the latest APK from the [Releases](https://github.com/pawelorzech/FastMask/releases) page
|
|
||||||
2. Enable "Install from unknown sources" for your browser or file manager
|
|
||||||
3. Open the APK file to install
|
|
||||||
|
|
||||||
### Build from Source
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/pawelorzech/FastMask.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Open the project in Android Studio
|
|
||||||
|
|
||||||
3. Build and run on your device or emulator
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1. Create a Fastmail API token:
|
### 1. Create a Fastmail API Token
|
||||||
- Log in to [Fastmail](https://www.fastmail.com)
|
|
||||||
- Go to **Settings** → **Privacy & Security** → **Integrations** → **API tokens**
|
|
||||||
- Click **New API token**
|
|
||||||
- Give it a name (e.g., "FastMask")
|
|
||||||
- Select the scope: **Masked Email** (read/write)
|
|
||||||
- Copy the generated token
|
|
||||||
|
|
||||||
2. Open FastMask and paste your API token to log in
|
1. Log in to [Fastmail](https://www.fastmail.com)
|
||||||
|
2. Navigate to **Settings** → **Privacy & Security** → **Integrations** → **API tokens**
|
||||||
|
3. Click **New API token**
|
||||||
|
4. Name it (e.g., "FastMask")
|
||||||
|
5. Select scope: **Masked Email** (read/write)
|
||||||
|
6. Copy the generated token
|
||||||
|
|
||||||
|
### 2. Log in to FastMask
|
||||||
|
|
||||||
|
1. Open the app
|
||||||
|
2. Paste your API token
|
||||||
|
3. Tap "Log in"
|
||||||
|
|
||||||
|
Your token is stored securely using Android's EncryptedSharedPreferences.
|
||||||
|
|
||||||
## Tech Stack
|
## Tech Stack
|
||||||
|
|
||||||
- **Kotlin** - 100% Kotlin codebase
|
| Category | Technology |
|
||||||
- **Jetpack Compose** - Modern declarative UI
|
|----------|------------|
|
||||||
- **Material 3** - Latest Material Design components
|
| **Language** | [Kotlin](https://kotlinlang.org/) 100% |
|
||||||
- **Hilt** - Dependency injection
|
| **UI Framework** | [Jetpack Compose](https://developer.android.com/jetpack/compose) |
|
||||||
- **Coroutines & Flow** - Asynchronous programming
|
| **Design System** | [Material 3](https://m3.material.io/) with dynamic theming |
|
||||||
- **Retrofit + OkHttp** - Network communication
|
| **DI** | [Hilt](https://dagger.dev/hilt/) |
|
||||||
- **Kotlinx Serialization** - JSON parsing
|
| **Networking** | [Retrofit](https://square.github.io/retrofit/) + [OkHttp](https://square.github.io/okhttp/) |
|
||||||
- **JMAP Protocol** - Fastmail's native API
|
| **Serialization** | [Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization) |
|
||||||
|
| **Async** | [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) + [Flow](https://kotlinlang.org/docs/flow.html) |
|
||||||
|
| **API Protocol** | [JMAP](https://jmap.io/) (Fastmail's native protocol) |
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
The app follows Clean Architecture principles with MVVM pattern:
|
The app follows **Clean Architecture** principles with **MVVM** pattern:
|
||||||
|
|
||||||
```
|
```
|
||||||
app/
|
app/
|
||||||
├── data/ # Data layer (API, repositories)
|
├── data/ # Data layer
|
||||||
├── domain/ # Business logic (use cases, models)
|
│ ├── api/ # JMAP API service & models
|
||||||
|
│ ├── local/ # Secure token storage
|
||||||
|
│ └── repository/ # Repository implementations
|
||||||
|
│
|
||||||
|
├── domain/ # Business logic layer
|
||||||
|
│ ├── model/ # Domain models
|
||||||
|
│ ├── repository/ # Repository interfaces
|
||||||
|
│ └── usecase/ # Use cases
|
||||||
|
│
|
||||||
├── di/ # Dependency injection modules
|
├── di/ # Dependency injection modules
|
||||||
└── ui/ # Presentation layer (screens, viewmodels)
|
│
|
||||||
|
└── ui/ # Presentation layer
|
||||||
|
├── auth/ # Login screen
|
||||||
|
├── list/ # Masked email list
|
||||||
|
├── create/ # Create new mask
|
||||||
|
├── detail/ # View/edit mask details
|
||||||
|
├── settings/ # Settings screen
|
||||||
|
├── components/ # Reusable UI components
|
||||||
|
├── navigation/ # Navigation setup
|
||||||
|
└── theme/ # Material 3 theming
|
||||||
```
|
```
|
||||||
|
|
||||||
## Privacy
|
## Privacy & Security
|
||||||
|
|
||||||
- Your API token is stored securely using Android's EncryptedSharedPreferences
|
- **Local Storage Only**: Your API token is stored locally using Android's [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences)
|
||||||
- The app communicates directly with Fastmail's API - no third-party servers
|
- **Direct API Communication**: The app communicates directly with Fastmail's servers - no intermediary servers
|
||||||
- No analytics or tracking
|
- **No Tracking**: Zero analytics, telemetry, or data collection
|
||||||
|
- **Open Source**: Full source code available for audit
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
Contributions are welcome! Here's how you can help:
|
||||||
|
|
||||||
|
1. **Fork** the repository
|
||||||
|
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. **Commit** your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
4. **Push** to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
5. **Open** a Pull Request
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
1. Install [Android Studio](https://developer.android.com/studio) (latest stable)
|
||||||
|
2. Clone the repository
|
||||||
|
3. Open the project in Android Studio
|
||||||
|
4. Sync Gradle and run on an emulator or device
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
- Follow [Kotlin coding conventions](https://kotlinlang.org/docs/coding-conventions.html)
|
||||||
|
- Use meaningful commit messages
|
||||||
|
- Write tests for new features when applicable
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.3 (January 2026)
|
||||||
|
- **New**: Settings screen with language picker, contact button, and logout
|
||||||
|
- **New**: Localization support for 20 languages (English, Chinese, Spanish, Hindi, Arabic, Portuguese, Bengali, Russian, Japanese, French, German, Korean, Italian, Turkish, Vietnamese, Polish, Ukrainian, Dutch, Thai, Indonesian)
|
||||||
|
- **New**: In-app language override using AppCompatDelegate
|
||||||
|
- **Improved**: All UI strings now use centralized string resources
|
||||||
|
- **Fixed**: Choppy navigation transitions between Settings and List screens
|
||||||
|
- **Fixed**: Language preference now persists correctly across app restarts
|
||||||
|
- **Improved**: Navigation animations simplified (slide-only, 220ms with FastOutSlowInEasing)
|
||||||
|
- **Improved**: Soft refresh prevents shimmer loading when returning from Settings
|
||||||
|
|
||||||
|
### v1.2 (January 2026)
|
||||||
|
- **Fixed**: Login crash caused by `ParameterizedType` casting error at runtime
|
||||||
|
- **Improved**: TokenStorage now uses lazy initialization for EncryptedSharedPreferences
|
||||||
|
- **Stability**: Deferred crypto initialization prevents reflection errors during Hilt injection
|
||||||
|
|
||||||
|
### v1.1 (January 2026)
|
||||||
|
- **Fixed**: ProGuard/R8 minification crash with `ParameterizedType` casting error
|
||||||
|
- **Improved**: Added proper ProGuard rules for Google Tink (security-crypto dependency)
|
||||||
|
- **Stability**: Release builds now work correctly with code minification enabled
|
||||||
|
|
||||||
|
### v1.0 (Initial Release)
|
||||||
|
- Manage Fastmail masked emails
|
||||||
|
- Create, edit, enable/disable, and delete masks
|
||||||
|
- Material 3 dynamic theming
|
||||||
|
- Secure token storage with EncryptedSharedPreferences
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
- [ ] Add screenshots to README
|
||||||
|
- [ ] Widget for quick mask creation
|
||||||
|
- [ ] Dark/light mode toggle
|
||||||
|
- [x] Localization support (20 languages)
|
||||||
|
- [x] Settings screen with language picker
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is open source. See the [LICENSE](LICENSE) file for details.
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
- [Fastmail](https://www.fastmail.com) for their excellent JMAP API
|
- [Fastmail](https://www.fastmail.com) for their excellent email service and JMAP API
|
||||||
- [JMAP](https://jmap.io) specification for masked emails
|
- [JMAP](https://jmap.io/) for the open standard specification
|
||||||
|
- The Android and Kotlin communities for amazing tools and libraries
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Made with Kotlin and Jetpack Compose
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/pawelorzech/FastMask/issues">Report Bug</a> •
|
||||||
|
<a href="https://github.com/pawelorzech/FastMask/issues">Request Feature</a>
|
||||||
|
</p>
|
||||||
|
|
|
||||||
78
SECURITY.md
Normal file
78
SECURITY.md
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
The FastMask team takes security seriously. We appreciate your efforts to responsibly disclose your findings.
|
||||||
|
|
||||||
|
### How to Report
|
||||||
|
|
||||||
|
If you discover a security vulnerability, please report it by:
|
||||||
|
|
||||||
|
1. **Opening a private security advisory** on GitHub:
|
||||||
|
- Go to the [Security tab](https://github.com/pawelorzech/FastMask/security/advisories)
|
||||||
|
- Click "New draft security advisory"
|
||||||
|
- Provide details about the vulnerability
|
||||||
|
|
||||||
|
2. **Or emailing directly** (if available in the repository owner's profile)
|
||||||
|
|
||||||
|
### What to Include
|
||||||
|
|
||||||
|
- Description of the vulnerability
|
||||||
|
- Steps to reproduce
|
||||||
|
- Potential impact
|
||||||
|
- Suggested fix (if any)
|
||||||
|
|
||||||
|
### Response Timeline
|
||||||
|
|
||||||
|
- **Acknowledgment**: Within 48 hours
|
||||||
|
- **Initial assessment**: Within 1 week
|
||||||
|
- **Resolution timeline**: Depends on severity, typically 30-90 days
|
||||||
|
|
||||||
|
## Security Measures in FastMask
|
||||||
|
|
||||||
|
### Data Storage
|
||||||
|
|
||||||
|
- API tokens are stored using Android's [EncryptedSharedPreferences](https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences)
|
||||||
|
- Encryption uses AES-256-GCM for values and AES-256-SIV for keys
|
||||||
|
- No sensitive data is stored in plain text
|
||||||
|
|
||||||
|
### Network Security
|
||||||
|
|
||||||
|
- All communication with Fastmail uses HTTPS/TLS
|
||||||
|
- Certificate pinning is recommended for production builds
|
||||||
|
- No data is sent to third-party servers
|
||||||
|
|
||||||
|
### Privacy
|
||||||
|
|
||||||
|
- No analytics or tracking
|
||||||
|
- No data collection
|
||||||
|
- Direct communication with Fastmail API only
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 1.x | :white_check_mark: |
|
||||||
|
|
||||||
|
## Best Practices for Users
|
||||||
|
|
||||||
|
1. **Protect your API token**: Treat it like a password
|
||||||
|
2. **Use device security**: Enable screen lock on your device
|
||||||
|
3. **Keep the app updated**: Install updates for security fixes
|
||||||
|
4. **Review permissions**: The app only requests necessary permissions
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The following are **in scope** for security reports:
|
||||||
|
|
||||||
|
- Authentication and authorization issues
|
||||||
|
- Data leakage or exposure
|
||||||
|
- Cryptographic weaknesses
|
||||||
|
- API security issues
|
||||||
|
|
||||||
|
The following are **out of scope**:
|
||||||
|
|
||||||
|
- Social engineering attacks
|
||||||
|
- Physical attacks on user devices
|
||||||
|
- Denial of service attacks
|
||||||
|
- Issues in third-party dependencies (report to upstream)
|
||||||
BIN
app/.DS_Store
vendored
Normal file
BIN
app/.DS_Store
vendored
Normal file
Binary file not shown.
|
|
@ -14,8 +14,8 @@ android {
|
||||||
applicationId = "com.fastmask"
|
applicationId = "com.fastmask"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 4
|
||||||
versionName = "1.0"
|
versionName = "1.3"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
@ -54,6 +54,7 @@ android {
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
composeOptions {
|
||||||
|
|
@ -70,16 +71,25 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
// Core Android
|
// Core Android
|
||||||
implementation("androidx.core:core-ktx:1.12.0")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
|
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||||
implementation("androidx.activity:activity-compose:1.8.2")
|
implementation("androidx.activity:activity-compose:1.9.0")
|
||||||
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
implementation(platform("androidx.compose:compose-bom:2024.09.00"))
|
implementation(platform("androidx.compose:compose-bom:2024.09.00"))
|
||||||
implementation("androidx.compose.ui:ui")
|
implementation("androidx.compose.ui:ui")
|
||||||
implementation("androidx.compose.ui:ui-graphics")
|
implementation("androidx.compose.ui:ui-graphics")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
|
implementation("androidx.compose.ui:ui-text-google-fonts:1.6.0")
|
||||||
implementation("androidx.compose.material3:material3")
|
implementation("androidx.compose.material3:material3")
|
||||||
implementation("androidx.compose.material:material-icons-extended")
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
|
implementation("androidx.compose.animation:animation")
|
||||||
|
|
||||||
|
// Large screen support
|
||||||
|
implementation("androidx.compose.material3.adaptive:adaptive:1.0.0")
|
||||||
|
implementation("androidx.compose.material3.adaptive:adaptive-layout:1.0.0")
|
||||||
|
implementation("androidx.compose.material3.adaptive:adaptive-navigation:1.0.0")
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
implementation("androidx.navigation:navigation-compose:2.7.6")
|
implementation("androidx.navigation:navigation-compose:2.7.6")
|
||||||
|
|
@ -122,3 +132,7 @@ dependencies {
|
||||||
kapt {
|
kapt {
|
||||||
correctErrorTypes = true
|
correctErrorTypes = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(17)
|
||||||
|
}
|
||||||
|
|
|
||||||
14
app/proguard-rules.pro
vendored
14
app/proguard-rules.pro
vendored
|
|
@ -38,8 +38,20 @@
|
||||||
-dontwarn okhttp3.**
|
-dontwarn okhttp3.**
|
||||||
-dontwarn okio.**
|
-dontwarn okio.**
|
||||||
|
|
||||||
# Google Tink / Error Prone annotations
|
# Google Tink (used by security-crypto)
|
||||||
|
-keep class com.google.crypto.tink.** { *; }
|
||||||
|
-keepclassmembers class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite {
|
||||||
|
<fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Tink optional dependencies (not used but referenced)
|
||||||
|
-dontwarn com.google.api.client.http.**
|
||||||
|
-dontwarn com.google.errorprone.annotations.InlineMe
|
||||||
|
-dontwarn org.joda.time.**
|
||||||
|
|
||||||
|
# Google Error Prone annotations
|
||||||
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
|
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
|
||||||
-dontwarn com.google.errorprone.annotations.CheckReturnValue
|
-dontwarn com.google.errorprone.annotations.CheckReturnValue
|
||||||
-dontwarn com.google.errorprone.annotations.Immutable
|
-dontwarn com.google.errorprone.annotations.Immutable
|
||||||
-dontwarn com.google.errorprone.annotations.RestrictedApi
|
-dontwarn com.google.errorprone.annotations.RestrictedApi
|
||||||
|
-dontwarn javax.annotation.**
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.FastMask"
|
android:theme="@style/Theme.FastMask"
|
||||||
|
|
@ -19,7 +20,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.FastMask">
|
android:theme="@style/Theme.FastMask.Splash">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,30 @@
|
||||||
package com.fastmask
|
package com.fastmask
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import com.fastmask.data.local.SettingsDataStore
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class FastMaskApplication : Application()
|
class FastMaskApplication : Application() {
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
// Restore saved language after super.onCreate() but before any Activity starts.
|
||||||
|
// We read directly from DataStore here since Hilt injection happens during super.onCreate().
|
||||||
|
restoreSavedLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreSavedLanguage() {
|
||||||
|
try {
|
||||||
|
val savedLanguageCode = SettingsDataStore.getLanguageBlocking(this)
|
||||||
|
if (savedLanguageCode != null) {
|
||||||
|
val localeList = LocaleListCompat.forLanguageTags(savedLanguageCode)
|
||||||
|
AppCompatDelegate.setApplicationLocales(localeList)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// If reading fails, use system default locale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package com.fastmask
|
package com.fastmask
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.fastmask.domain.repository.AuthRepository
|
import com.fastmask.domain.repository.AuthRepository
|
||||||
import com.fastmask.ui.navigation.FastMaskNavHost
|
import com.fastmask.ui.navigation.FastMaskNavHost
|
||||||
|
|
@ -17,15 +18,28 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var authRepository: AuthRepository
|
lateinit var authRepository: AuthRepository
|
||||||
|
|
||||||
|
private var isReady = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
val splashScreen = installSplashScreen()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
splashScreen.setKeepOnScreenCondition { !isReady }
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
val startDestination = if (authRepository.isLoggedIn()) {
|
||||||
|
NavRoutes.EMAIL_LIST
|
||||||
|
} else {
|
||||||
|
NavRoutes.LOGIN
|
||||||
|
}
|
||||||
|
isReady = true
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
FastMaskTheme {
|
FastMaskTheme {
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -33,11 +47,6 @@ class MainActivity : ComponentActivity() {
|
||||||
color = MaterialTheme.colorScheme.background
|
color = MaterialTheme.colorScheme.background
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val startDestination = if (authRepository.isLoggedIn()) {
|
|
||||||
NavRoutes.EMAIL_LIST
|
|
||||||
} else {
|
|
||||||
NavRoutes.LOGIN
|
|
||||||
}
|
|
||||||
|
|
||||||
FastMaskNavHost(
|
FastMaskNavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package com.fastmask.data.local
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class SettingsDataStore @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context
|
||||||
|
) {
|
||||||
|
private val languageKey = stringPreferencesKey("language_code")
|
||||||
|
|
||||||
|
val languageFlow: Flow<String?> = context.settingsDataStore.data.map { preferences ->
|
||||||
|
preferences[languageKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setLanguage(languageCode: String?) {
|
||||||
|
context.settingsDataStore.edit { preferences ->
|
||||||
|
if (languageCode == null) {
|
||||||
|
preferences.remove(languageKey)
|
||||||
|
} else {
|
||||||
|
preferences[languageKey] = languageCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLanguageBlocking(): String? {
|
||||||
|
return runBlocking {
|
||||||
|
context.settingsDataStore.data.first()[languageKey]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LANGUAGE_KEY = stringPreferencesKey("language_code")
|
||||||
|
|
||||||
|
fun getLanguageBlocking(context: Context): String? {
|
||||||
|
return runBlocking {
|
||||||
|
context.settingsDataStore.data.first()[LANGUAGE_KEY]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,17 +12,19 @@ import javax.inject.Singleton
|
||||||
class TokenStorage @Inject constructor(
|
class TokenStorage @Inject constructor(
|
||||||
@ApplicationContext private val context: Context
|
@ApplicationContext private val context: Context
|
||||||
) {
|
) {
|
||||||
private val masterKey = MasterKey.Builder(context)
|
private val sharedPreferences: SharedPreferences by lazy {
|
||||||
|
val masterKey = MasterKey.Builder(context)
|
||||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val sharedPreferences: SharedPreferences = EncryptedSharedPreferences.create(
|
EncryptedSharedPreferences.create(
|
||||||
context,
|
context,
|
||||||
PREFS_FILE_NAME,
|
PREFS_FILE_NAME,
|
||||||
masterKey,
|
masterKey,
|
||||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveToken(token: String) {
|
fun saveToken(token: String) {
|
||||||
sharedPreferences.edit().putString(KEY_API_TOKEN, token).apply()
|
sharedPreferences.edit().putString(KEY_API_TOKEN, token).apply()
|
||||||
|
|
|
||||||
35
app/src/main/java/com/fastmask/domain/model/Language.kt
Normal file
35
app/src/main/java/com/fastmask/domain/model/Language.kt
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.fastmask.domain.model
|
||||||
|
|
||||||
|
import com.fastmask.R
|
||||||
|
|
||||||
|
enum class Language(
|
||||||
|
val code: String,
|
||||||
|
val displayNameRes: Int
|
||||||
|
) {
|
||||||
|
ENGLISH("en", R.string.language_en),
|
||||||
|
CHINESE("zh", R.string.language_zh),
|
||||||
|
SPANISH("es", R.string.language_es),
|
||||||
|
HINDI("hi", R.string.language_hi),
|
||||||
|
ARABIC("ar", R.string.language_ar),
|
||||||
|
PORTUGUESE("pt", R.string.language_pt),
|
||||||
|
BENGALI("bn", R.string.language_bn),
|
||||||
|
RUSSIAN("ru", R.string.language_ru),
|
||||||
|
JAPANESE("ja", R.string.language_ja),
|
||||||
|
FRENCH("fr", R.string.language_fr),
|
||||||
|
GERMAN("de", R.string.language_de),
|
||||||
|
KOREAN("ko", R.string.language_ko),
|
||||||
|
ITALIAN("it", R.string.language_it),
|
||||||
|
TURKISH("tr", R.string.language_tr),
|
||||||
|
VIETNAMESE("vi", R.string.language_vi),
|
||||||
|
POLISH("pl", R.string.language_pl),
|
||||||
|
UKRAINIAN("uk", R.string.language_uk),
|
||||||
|
DUTCH("nl", R.string.language_nl),
|
||||||
|
THAI("th", R.string.language_th),
|
||||||
|
INDONESIAN("id", R.string.language_id);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromCode(code: String?): Language? {
|
||||||
|
return entries.find { it.code == code }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,26 +15,23 @@ data class MaskedEmail(
|
||||||
val url: String?,
|
val url: String?,
|
||||||
val emailPrefix: String?,
|
val emailPrefix: String?,
|
||||||
val createdAt: Instant?,
|
val createdAt: Instant?,
|
||||||
val lastMessageAt: Instant?
|
val lastMessageAt: Instant?,
|
||||||
|
val formattedCreatedAt: String? = createdAt?.let { formatInstant(it) },
|
||||||
|
val formattedLastMessageAt: String? = lastMessageAt?.let { formatInstant(it) }
|
||||||
) {
|
) {
|
||||||
val displayName: String
|
val displayName: String
|
||||||
get() = description?.takeIf { it.isNotBlank() }
|
get() = description?.takeIf { it.isNotBlank() }
|
||||||
?: forDomain?.takeIf { it.isNotBlank() }
|
?: forDomain?.takeIf { it.isNotBlank() }
|
||||||
?: email.substringBefore("@")
|
?: email.substringBefore("@")
|
||||||
|
|
||||||
val formattedCreatedAt: String?
|
|
||||||
get() = createdAt?.let { formatInstant(it) }
|
|
||||||
|
|
||||||
val formattedLastMessageAt: String?
|
|
||||||
get() = lastMessageAt?.let { formatInstant(it) }
|
|
||||||
|
|
||||||
val isActive: Boolean
|
val isActive: Boolean
|
||||||
get() = state == EmailState.ENABLED || state == EmailState.PENDING
|
get() = state == EmailState.ENABLED || state == EmailState.PENDING
|
||||||
|
|
||||||
private fun formatInstant(instant: Instant): String {
|
companion object {
|
||||||
val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
private val formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
|
||||||
.withZone(ZoneId.systemDefault())
|
.withZone(ZoneId.systemDefault())
|
||||||
return formatter.format(instant)
|
|
||||||
|
fun formatInstant(instant: Instant): String = formatter.format(instant)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.fastmask.domain.usecase
|
||||||
|
|
||||||
|
import com.fastmask.data.local.SettingsDataStore
|
||||||
|
import com.fastmask.domain.model.Language
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetCurrentLanguageUseCase @Inject constructor(
|
||||||
|
private val settingsDataStore: SettingsDataStore
|
||||||
|
) {
|
||||||
|
operator fun invoke(): Flow<Language?> {
|
||||||
|
return settingsDataStore.languageFlow.map { code ->
|
||||||
|
Language.fromCode(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBlocking(): Language? {
|
||||||
|
return Language.fromCode(settingsDataStore.getLanguageBlocking())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.fastmask.domain.usecase
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import com.fastmask.data.local.SettingsDataStore
|
||||||
|
import com.fastmask.domain.model.Language
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SetLanguageUseCase @Inject constructor(
|
||||||
|
private val settingsDataStore: SettingsDataStore
|
||||||
|
) {
|
||||||
|
suspend operator fun invoke(language: Language?) {
|
||||||
|
val languageCode = language?.code
|
||||||
|
settingsDataStore.setLanguage(languageCode)
|
||||||
|
|
||||||
|
val localeList = if (languageCode != null) {
|
||||||
|
LocaleListCompat.forLanguageTags(languageCode)
|
||||||
|
} else {
|
||||||
|
LocaleListCompat.getEmptyLocaleList()
|
||||||
|
}
|
||||||
|
AppCompatDelegate.setApplicationLocales(localeList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
|
@ -42,6 +43,7 @@ import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.fastmask.R
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -80,13 +82,13 @@ fun LoginScreen(
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "FastMask",
|
text = stringResource(R.string.app_name),
|
||||||
style = MaterialTheme.typography.headlineLarge,
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Fastmail Masked Email Manager",
|
text = stringResource(R.string.login_subtitle),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
@ -96,8 +98,8 @@ fun LoginScreen(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.token,
|
value = uiState.token,
|
||||||
onValueChange = viewModel::onTokenChange,
|
onValueChange = viewModel::onTokenChange,
|
||||||
label = { Text("API Token") },
|
label = { Text(stringResource(R.string.login_api_token_label)) },
|
||||||
placeholder = { Text("Enter your Fastmail API token") },
|
placeholder = { Text(stringResource(R.string.login_api_token_placeholder)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
visualTransformation = if (showToken) VisualTransformation.None else PasswordVisualTransformation(),
|
visualTransformation = if (showToken) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
|
|
@ -112,7 +114,11 @@ fun LoginScreen(
|
||||||
IconButton(onClick = { showToken = !showToken }) {
|
IconButton(onClick = { showToken = !showToken }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (showToken) Icons.Default.VisibilityOff else Icons.Default.Visibility,
|
imageVector = if (showToken) Icons.Default.VisibilityOff else Icons.Default.Visibility,
|
||||||
contentDescription = if (showToken) "Hide token" else "Show token"
|
contentDescription = if (showToken) {
|
||||||
|
stringResource(R.string.login_hide_token)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.login_show_token)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -143,7 +149,7 @@ fun LoginScreen(
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text("Login")
|
Text(stringResource(R.string.login_button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,17 +165,13 @@ fun LoginScreen(
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "How to get your API token:",
|
text = stringResource(R.string.login_instructions_title),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "1. Log in to Fastmail web app\n" +
|
text = stringResource(R.string.login_instructions_full),
|
||||||
"2. Go to Settings > Privacy & Security\n" +
|
|
||||||
"3. Click on Integrations > API tokens\n" +
|
|
||||||
"4. Create a new token with \"Masked Email\" scope\n" +
|
|
||||||
"5. Copy the token and paste it above",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Start
|
textAlign = TextAlign.Start
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,10 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.fastmask.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorMessage(
|
fun ErrorMessage(
|
||||||
|
|
@ -48,7 +50,7 @@ fun ErrorMessage(
|
||||||
if (onRetry != null) {
|
if (onRetry != null) {
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Button(onClick = onRetry) {
|
Button(onClick = onRetry) {
|
||||||
Text("Retry")
|
Text(stringResource(R.string.error_retry))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
package com.fastmask.ui.components
|
package com.fastmask.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -8,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
|
@ -21,26 +27,61 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.fastmask.R
|
||||||
import com.fastmask.domain.model.EmailState
|
import com.fastmask.domain.model.EmailState
|
||||||
import com.fastmask.domain.model.MaskedEmail
|
import com.fastmask.domain.model.MaskedEmail
|
||||||
import com.fastmask.ui.theme.DeletedRed
|
import com.fastmask.ui.theme.FastMaskStatusColors
|
||||||
import com.fastmask.ui.theme.DisabledGray
|
|
||||||
import com.fastmask.ui.theme.EnabledGreen
|
|
||||||
import com.fastmask.ui.theme.PendingOrange
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MaskedEmailCard(
|
fun MaskedEmailCard(
|
||||||
maskedEmail: MaskedEmail,
|
maskedEmail: MaskedEmail,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedContentScope: AnimatedContentScope,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isScrolling: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val stateDescription = when (maskedEmail.state) {
|
||||||
|
EmailState.ENABLED -> stringResource(R.string.state_enabled)
|
||||||
|
EmailState.DISABLED -> stringResource(R.string.state_disabled)
|
||||||
|
EmailState.DELETED -> stringResource(R.string.state_deleted)
|
||||||
|
EmailState.PENDING -> stringResource(R.string.state_pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
with(sharedTransitionScope) {
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick),
|
.then(
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
if (!isScrolling) {
|
||||||
|
Modifier.sharedBounds(
|
||||||
|
sharedContentState = rememberSharedContentState(key = "card-${maskedEmail.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
|
} else Modifier
|
||||||
|
)
|
||||||
|
.clickable {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
.semantics {
|
||||||
|
contentDescription = "${maskedEmail.displayName}, ${maskedEmail.email}, $stateDescription"
|
||||||
|
},
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -48,21 +89,41 @@ fun MaskedEmailCard(
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
StatusIcon(state = maskedEmail.state)
|
StatusIcon(
|
||||||
|
state = maskedEmail.state,
|
||||||
|
modifier = if (!isScrolling) {
|
||||||
|
Modifier.sharedElement(
|
||||||
|
state = rememberSharedContentState(key = "icon-${maskedEmail.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
|
} else Modifier
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = maskedEmail.displayName,
|
text = maskedEmail.displayName,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = if (!isScrolling) {
|
||||||
|
Modifier.sharedElement(
|
||||||
|
state = rememberSharedContentState(key = "title-${maskedEmail.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
|
} else Modifier
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = maskedEmail.email,
|
text = maskedEmail.email,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = if (!isScrolling) {
|
||||||
|
Modifier.sharedElement(
|
||||||
|
state = rememberSharedContentState(key = "email-${maskedEmail.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
|
} else Modifier
|
||||||
)
|
)
|
||||||
maskedEmail.forDomain?.let { domain ->
|
maskedEmail.forDomain?.let { domain ->
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -77,20 +138,34 @@ fun MaskedEmailCard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun StatusIcon(state: EmailState) {
|
|
||||||
val (icon, color) = when (state) {
|
|
||||||
EmailState.ENABLED -> Icons.Default.Check to EnabledGreen
|
|
||||||
EmailState.DISABLED -> Icons.Default.Close to DisabledGray
|
|
||||||
EmailState.DELETED -> Icons.Default.Delete to DeletedRed
|
|
||||||
EmailState.PENDING -> Icons.Default.HourglassEmpty to PendingOrange
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StatusIcon(
|
||||||
|
state: EmailState,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val statusColors = FastMaskStatusColors.current
|
||||||
|
|
||||||
|
val (icon, colorPair) = when (state) {
|
||||||
|
EmailState.ENABLED -> Icons.Default.Check to statusColors.enabled
|
||||||
|
EmailState.DISABLED -> Icons.Default.Close to statusColors.disabled
|
||||||
|
EmailState.DELETED -> Icons.Default.Delete to statusColors.deleted
|
||||||
|
EmailState.PENDING -> Icons.Default.HourglassEmpty to statusColors.pending
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(colorPair.container),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = state.name,
|
contentDescription = state.name,
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
tint = color
|
tint = colorPair.content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
131
app/src/main/java/com/fastmask/ui/components/ShimmerEffect.kt
Normal file
131
app/src/main/java/com/fastmask/ui/components/ShimmerEffect.kt
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
package com.fastmask.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShimmerEmailCard(
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val shimmerColors = listOf(
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.6f),
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerHighest.copy(alpha = 0.2f),
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
|
||||||
|
val transition = rememberInfiniteTransition(label = "shimmer")
|
||||||
|
val translateAnim by transition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 1000f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(durationMillis = 1000, easing = LinearEasing),
|
||||||
|
repeatMode = RepeatMode.Restart
|
||||||
|
),
|
||||||
|
label = "shimmer_translate"
|
||||||
|
)
|
||||||
|
|
||||||
|
val brush = Brush.linearGradient(
|
||||||
|
colors = shimmerColors,
|
||||||
|
start = Offset(translateAnim - 500f, translateAnim - 500f),
|
||||||
|
end = Offset(translateAnim, translateAnim)
|
||||||
|
)
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// Status icon placeholder
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(brush)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
// Title placeholder
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.6f)
|
||||||
|
.height(20.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(brush)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
// Email placeholder
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.8f)
|
||||||
|
.height(16.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(brush)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
// Domain placeholder
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.4f)
|
||||||
|
.height(14.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(brush)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShimmerEmailList(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
itemCount: Int = 8
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
items(itemCount) {
|
||||||
|
ShimmerEmailCard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.fastmask.ui.create
|
package com.fastmask.ui.create
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -22,6 +24,10 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
|
@ -29,11 +35,18 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.fastmask.R
|
||||||
import com.fastmask.domain.model.EmailState
|
import com.fastmask.domain.model.EmailState
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
|
|
@ -45,16 +58,27 @@ fun CreateMaskedEmailScreen(
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val createdMessage = stringResource(R.string.create_email_created, "%s")
|
||||||
|
val copyAction = stringResource(R.string.create_email_copy_action)
|
||||||
|
val navigateBackDesc = stringResource(R.string.navigate_back)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is CreateMaskedEmailEvent.Created -> {
|
is CreateMaskedEmailEvent.Created -> {
|
||||||
Toast.makeText(
|
val result = snackbarHostState.showSnackbar(
|
||||||
context,
|
message = createdMessage.replace("%s", event.email),
|
||||||
"Created: ${event.email}",
|
actionLabel = copyAction,
|
||||||
Toast.LENGTH_LONG
|
duration = SnackbarDuration.Long
|
||||||
).show()
|
)
|
||||||
|
if (result == SnackbarResult.ActionPerformed) {
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("Email", event.email)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
}
|
||||||
onNavigateBack()
|
onNavigateBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,20 +88,31 @@ fun CreateMaskedEmailScreen(
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Create Masked Email") },
|
title = { Text(stringResource(R.string.create_email_title)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onNavigateBack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = navigateBackDesc
|
||||||
|
}
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Back"
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
titleContentColor = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -90,12 +125,12 @@ fun CreateMaskedEmailScreen(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.emailPrefix,
|
value = uiState.emailPrefix,
|
||||||
onValueChange = viewModel::onPrefixChange,
|
onValueChange = viewModel::onPrefixChange,
|
||||||
label = { Text("Email Prefix (optional)") },
|
label = { Text(stringResource(R.string.create_email_prefix_label)) },
|
||||||
placeholder = { Text("e.g., mysite_shopping") },
|
placeholder = { Text(stringResource(R.string.create_email_prefix_placeholder)) },
|
||||||
supportingText = {
|
supportingText = {
|
||||||
Text(
|
Text(
|
||||||
text = uiState.prefixError
|
text = uiState.prefixError
|
||||||
?: "Max 64 chars: lowercase letters, numbers, underscores"
|
?: stringResource(R.string.create_email_prefix_hint)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
isError = uiState.prefixError != null,
|
isError = uiState.prefixError != null,
|
||||||
|
|
@ -109,8 +144,8 @@ fun CreateMaskedEmailScreen(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.forDomain,
|
value = uiState.forDomain,
|
||||||
onValueChange = viewModel::onDomainChange,
|
onValueChange = viewModel::onDomainChange,
|
||||||
label = { Text("Associated Domain (optional)") },
|
label = { Text(stringResource(R.string.create_email_domain_label)) },
|
||||||
placeholder = { Text("e.g., example.com") },
|
placeholder = { Text(stringResource(R.string.create_email_domain_placeholder)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isLoading
|
enabled = !uiState.isLoading
|
||||||
|
|
@ -121,8 +156,8 @@ fun CreateMaskedEmailScreen(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.description,
|
value = uiState.description,
|
||||||
onValueChange = viewModel::onDescriptionChange,
|
onValueChange = viewModel::onDescriptionChange,
|
||||||
label = { Text("Description (optional)") },
|
label = { Text(stringResource(R.string.create_email_description_label)) },
|
||||||
placeholder = { Text("e.g., Shopping account") },
|
placeholder = { Text(stringResource(R.string.create_email_description_placeholder)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isLoading
|
enabled = !uiState.isLoading
|
||||||
|
|
@ -133,8 +168,8 @@ fun CreateMaskedEmailScreen(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.url,
|
value = uiState.url,
|
||||||
onValueChange = viewModel::onUrlChange,
|
onValueChange = viewModel::onUrlChange,
|
||||||
label = { Text("URL (optional)") },
|
label = { Text(stringResource(R.string.create_email_url_label)) },
|
||||||
placeholder = { Text("e.g., https://example.com") },
|
placeholder = { Text(stringResource(R.string.create_email_url_placeholder)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isLoading
|
enabled = !uiState.isLoading
|
||||||
|
|
@ -143,7 +178,7 @@ fun CreateMaskedEmailScreen(
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Initial State",
|
text = stringResource(R.string.create_email_initial_state),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -156,11 +191,18 @@ fun CreateMaskedEmailScreen(
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
selected = uiState.initialState == state,
|
selected = uiState.initialState == state,
|
||||||
onClick = { viewModel.onStateChange(state) },
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
viewModel.onStateChange(state)
|
||||||
|
},
|
||||||
enabled = !uiState.isLoading
|
enabled = !uiState.isLoading
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = state.name.lowercase().replaceFirstChar { it.uppercase() },
|
text = when (state) {
|
||||||
|
EmailState.ENABLED -> stringResource(R.string.state_enabled)
|
||||||
|
EmailState.DISABLED -> stringResource(R.string.state_disabled)
|
||||||
|
else -> state.name.lowercase().replaceFirstChar { it.uppercase() }
|
||||||
|
},
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.bodyLarge
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -178,7 +220,10 @@ fun CreateMaskedEmailScreen(
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = viewModel::create,
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
viewModel.create()
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isLoading && uiState.prefixError == null
|
enabled = !uiState.isLoading && uiState.prefixError == null
|
||||||
) {
|
) {
|
||||||
|
|
@ -189,7 +234,7 @@ fun CreateMaskedEmailScreen(
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text("Create Masked Email")
|
Text(stringResource(R.string.create_email_button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,16 @@ package com.fastmask.ui.detail
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -15,11 +23,15 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.HourglassEmpty
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
|
@ -33,6 +45,9 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarHost
|
||||||
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
|
|
@ -43,40 +58,64 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.fastmask.R
|
||||||
import com.fastmask.domain.model.EmailState
|
import com.fastmask.domain.model.EmailState
|
||||||
import com.fastmask.ui.components.ErrorMessage
|
import com.fastmask.ui.components.ErrorMessage
|
||||||
import com.fastmask.ui.components.LoadingIndicator
|
import com.fastmask.ui.components.LoadingIndicator
|
||||||
import com.fastmask.ui.theme.DeletedRed
|
import com.fastmask.ui.theme.FastMaskStatusColors
|
||||||
import com.fastmask.ui.theme.DisabledGray
|
|
||||||
import com.fastmask.ui.theme.EnabledGreen
|
|
||||||
import com.fastmask.ui.theme.PendingOrange
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MaskedEmailDetailScreen(
|
fun MaskedEmailDetailScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedContentScope: AnimatedContentScope,
|
||||||
viewModel: MaskedEmailDetailViewModel = hiltViewModel()
|
viewModel: MaskedEmailDetailViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val updatedMessage = stringResource(R.string.email_detail_updated)
|
||||||
|
val deletedMessage = stringResource(R.string.email_detail_deleted)
|
||||||
|
val copiedMessage = stringResource(R.string.email_detail_copied)
|
||||||
|
val navigateBackDesc = stringResource(R.string.navigate_back)
|
||||||
|
val deleteEmailDesc = stringResource(R.string.email_detail_delete)
|
||||||
|
val copyEmailDesc = stringResource(R.string.email_detail_copy_email)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.events.collectLatest { event ->
|
viewModel.events.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
is MaskedEmailDetailEvent.Updated -> {
|
is MaskedEmailDetailEvent.Updated -> {
|
||||||
Toast.makeText(context, "Updated successfully", Toast.LENGTH_SHORT).show()
|
snackbarHostState.showSnackbar(
|
||||||
|
message = updatedMessage,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is MaskedEmailDetailEvent.Deleted -> {
|
is MaskedEmailDetailEvent.Deleted -> {
|
||||||
Toast.makeText(context, "Deleted successfully", Toast.LENGTH_SHORT).show()
|
snackbarHostState.showSnackbar(
|
||||||
|
message = deletedMessage,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
onNavigateBack()
|
onNavigateBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,8 +125,8 @@ fun MaskedEmailDetailScreen(
|
||||||
if (showDeleteDialog) {
|
if (showDeleteDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showDeleteDialog = false },
|
onDismissRequest = { showDeleteDialog = false },
|
||||||
title = { Text("Delete Masked Email") },
|
title = { Text(stringResource(R.string.email_detail_delete_dialog_title)) },
|
||||||
text = { Text("Are you sure you want to delete this masked email? This action cannot be undone.") },
|
text = { Text(stringResource(R.string.email_detail_delete_dialog_message)) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -98,12 +137,12 @@ fun MaskedEmailDetailScreen(
|
||||||
contentColor = MaterialTheme.colorScheme.error
|
contentColor = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text("Delete")
|
Text(stringResource(R.string.email_detail_delete_confirm))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showDeleteDialog = false }) {
|
TextButton(onClick = { showDeleteDialog = false }) {
|
||||||
Text("Cancel")
|
Text(stringResource(R.string.email_detail_delete_cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -112,41 +151,67 @@ fun MaskedEmailDetailScreen(
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Email Details") },
|
title = { Text(stringResource(R.string.email_detail_title)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onNavigateBack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = navigateBackDesc
|
||||||
|
}
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Back"
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
if (uiState.email != null) {
|
if (uiState.email != null) {
|
||||||
IconButton(onClick = { showDeleteDialog = true }) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
showDeleteDialog = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = deleteEmailDesc
|
||||||
|
}
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
contentDescription = "Delete",
|
contentDescription = null,
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
titleContentColor = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
snackbarHost = {
|
||||||
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = Triple(uiState.isLoading && uiState.email == null, uiState.error != null && uiState.email == null, uiState.email != null),
|
||||||
|
transitionSpec = {
|
||||||
|
fadeIn() togetherWith fadeOut()
|
||||||
|
},
|
||||||
|
label = "content_state"
|
||||||
|
) { (isLoading, hasError, hasEmail) ->
|
||||||
when {
|
when {
|
||||||
uiState.isLoading && uiState.email == null -> {
|
isLoading -> {
|
||||||
LoadingIndicator(
|
LoadingIndicator(
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
uiState.error != null && uiState.email == null -> {
|
hasError -> {
|
||||||
ErrorMessage(
|
ErrorMessage(
|
||||||
message = uiState.error!!,
|
message = uiState.error!!,
|
||||||
onRetry = viewModel::loadEmail,
|
onRetry = viewModel::loadEmail,
|
||||||
|
|
@ -154,7 +219,7 @@ fun MaskedEmailDetailScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
uiState.email != null -> {
|
hasEmail -> {
|
||||||
EmailDetailContent(
|
EmailDetailContent(
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
onDescriptionChange = viewModel::onDescriptionChange,
|
onDescriptionChange = viewModel::onDescriptionChange,
|
||||||
|
|
@ -162,13 +227,29 @@ fun MaskedEmailDetailScreen(
|
||||||
onUrlChange = viewModel::onUrlChange,
|
onUrlChange = viewModel::onUrlChange,
|
||||||
onToggleState = viewModel::toggleState,
|
onToggleState = viewModel::toggleState,
|
||||||
onSaveChanges = viewModel::saveChanges,
|
onSaveChanges = viewModel::saveChanges,
|
||||||
|
onCopyEmail = { email ->
|
||||||
|
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("Email", email)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = copiedMessage,
|
||||||
|
duration = SnackbarDuration.Short
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copyEmailDesc = copyEmailDesc,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = animatedContentScope,
|
||||||
modifier = Modifier.padding(paddingValues)
|
modifier = Modifier.padding(paddingValues)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun EmailDetailContent(
|
private fun EmailDetailContent(
|
||||||
uiState: MaskedEmailDetailUiState,
|
uiState: MaskedEmailDetailUiState,
|
||||||
|
|
@ -177,11 +258,24 @@ private fun EmailDetailContent(
|
||||||
onUrlChange: (String) -> Unit,
|
onUrlChange: (String) -> Unit,
|
||||||
onToggleState: () -> Unit,
|
onToggleState: () -> Unit,
|
||||||
onSaveChanges: () -> Unit,
|
onSaveChanges: () -> Unit,
|
||||||
|
onCopyEmail: (String) -> Unit,
|
||||||
|
copyEmailDesc: String,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedContentScope: AnimatedContentScope,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val email = uiState.email!!
|
val email = uiState.email!!
|
||||||
val context = LocalContext.current
|
val statusColors = FastMaskStatusColors.current
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
val colorPair = when (email.state) {
|
||||||
|
EmailState.ENABLED -> statusColors.enabled
|
||||||
|
EmailState.DISABLED -> statusColors.disabled
|
||||||
|
EmailState.DELETED -> statusColors.deleted
|
||||||
|
EmailState.PENDING -> statusColors.pending
|
||||||
|
}
|
||||||
|
|
||||||
|
with(sharedTransitionScope) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|
@ -189,9 +283,14 @@ private fun EmailDetailContent(
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.sharedBounds(
|
||||||
|
sharedContentState = rememberSharedContentState(key = "card-${email.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
),
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -201,29 +300,45 @@ private fun EmailDetailContent(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
StatusIcon(
|
||||||
|
state = email.state,
|
||||||
|
modifier = Modifier.sharedElement(
|
||||||
|
state = rememberSharedContentState(key = "icon-${email.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Text(
|
Text(
|
||||||
text = "Email Address",
|
text = email.displayName,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
modifier = Modifier.sharedElement(
|
||||||
|
state = rememberSharedContentState(key = "title-${email.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = email.email,
|
text = email.email,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
fontWeight = FontWeight.Bold
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.sharedElement(
|
||||||
|
state = rememberSharedContentState(key = "email-${email.id}"),
|
||||||
|
animatedVisibilityScope = animatedContentScope
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
val clip = ClipData.newPlainText("Email", email.email)
|
onCopyEmail(email.email)
|
||||||
clipboard.setPrimaryClip(clip)
|
},
|
||||||
Toast.makeText(context, "Copied to clipboard", Toast.LENGTH_SHORT).show()
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = copyEmailDesc
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ContentCopy,
|
imageVector = Icons.Default.ContentCopy,
|
||||||
contentDescription = "Copy email"
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -234,19 +349,23 @@ private fun EmailDetailContent(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Status: ",
|
text = stringResource(R.string.email_detail_status),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
val (statusText, statusColor) = when (email.state) {
|
Text(
|
||||||
EmailState.ENABLED -> "Enabled" to EnabledGreen
|
text = " ",
|
||||||
EmailState.DISABLED -> "Disabled" to DisabledGray
|
style = MaterialTheme.typography.bodyMedium
|
||||||
EmailState.DELETED -> "Deleted" to DeletedRed
|
)
|
||||||
EmailState.PENDING -> "Pending" to PendingOrange
|
val statusText = when (email.state) {
|
||||||
|
EmailState.ENABLED -> stringResource(R.string.state_enabled)
|
||||||
|
EmailState.DISABLED -> stringResource(R.string.state_disabled)
|
||||||
|
EmailState.DELETED -> stringResource(R.string.state_deleted)
|
||||||
|
EmailState.PENDING -> stringResource(R.string.state_pending)
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = statusText,
|
text = statusText,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = statusColor,
|
color = colorPair.content,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +373,7 @@ private fun EmailDetailContent(
|
||||||
email.createdBy?.let { createdBy ->
|
email.createdBy?.let { createdBy ->
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Created by: $createdBy",
|
text = stringResource(R.string.email_detail_created_by, createdBy),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
@ -263,7 +382,7 @@ private fun EmailDetailContent(
|
||||||
email.formattedCreatedAt?.let { createdAt ->
|
email.formattedCreatedAt?.let { createdAt ->
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Created: $createdAt",
|
text = stringResource(R.string.email_detail_created, createdAt),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
@ -272,7 +391,7 @@ private fun EmailDetailContent(
|
||||||
email.formattedLastMessageAt?.let { lastMessage ->
|
email.formattedLastMessageAt?.let { lastMessage ->
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Last message: $lastMessage",
|
text = stringResource(R.string.email_detail_last_message, lastMessage),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|
@ -288,11 +407,15 @@ private fun EmailDetailContent(
|
||||||
) {
|
) {
|
||||||
if (email.state == EmailState.DISABLED || email.state == EmailState.DELETED) {
|
if (email.state == EmailState.DISABLED || email.state == EmailState.DELETED) {
|
||||||
Button(
|
Button(
|
||||||
onClick = onToggleState,
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onToggleState()
|
||||||
|
},
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = !uiState.isUpdating,
|
enabled = !uiState.isUpdating,
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
containerColor = EnabledGreen
|
containerColor = statusColors.enabled.container,
|
||||||
|
contentColor = statusColors.enabled.content
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (uiState.isUpdating) {
|
if (uiState.isUpdating) {
|
||||||
|
|
@ -301,13 +424,16 @@ private fun EmailDetailContent(
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text("Enable")
|
Text(stringResource(R.string.email_detail_enable))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (email.state == EmailState.ENABLED || email.state == EmailState.PENDING) {
|
if (email.state == EmailState.ENABLED || email.state == EmailState.PENDING) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = onToggleState,
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onToggleState()
|
||||||
|
},
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = !uiState.isUpdating
|
enabled = !uiState.isUpdating
|
||||||
) {
|
) {
|
||||||
|
|
@ -317,7 +443,7 @@ private fun EmailDetailContent(
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text("Disable")
|
Text(stringResource(R.string.email_detail_disable))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +452,7 @@ private fun EmailDetailContent(
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Edit Details",
|
text = stringResource(R.string.email_detail_edit_title),
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -335,7 +461,7 @@ private fun EmailDetailContent(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.editedDescription,
|
value = uiState.editedDescription,
|
||||||
onValueChange = onDescriptionChange,
|
onValueChange = onDescriptionChange,
|
||||||
label = { Text("Description") },
|
label = { Text(stringResource(R.string.email_detail_description_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isUpdating
|
enabled = !uiState.isUpdating
|
||||||
|
|
@ -346,7 +472,7 @@ private fun EmailDetailContent(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.editedForDomain,
|
value = uiState.editedForDomain,
|
||||||
onValueChange = onForDomainChange,
|
onValueChange = onForDomainChange,
|
||||||
label = { Text("Associated Domain") },
|
label = { Text(stringResource(R.string.email_detail_domain_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isUpdating
|
enabled = !uiState.isUpdating
|
||||||
|
|
@ -357,16 +483,16 @@ private fun EmailDetailContent(
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = uiState.editedUrl,
|
value = uiState.editedUrl,
|
||||||
onValueChange = onUrlChange,
|
onValueChange = onUrlChange,
|
||||||
label = { Text("URL") },
|
label = { Text(stringResource(R.string.email_detail_url_label)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isUpdating
|
enabled = !uiState.isUpdating
|
||||||
)
|
)
|
||||||
|
|
||||||
if (uiState.error != null) {
|
uiState.error?.let { error ->
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
Text(
|
Text(
|
||||||
text = uiState.error!!,
|
text = error,
|
||||||
color = MaterialTheme.colorScheme.error,
|
color = MaterialTheme.colorScheme.error,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
|
|
@ -375,7 +501,10 @@ private fun EmailDetailContent(
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = onSaveChanges,
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onSaveChanges()
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
enabled = !uiState.isUpdating
|
enabled = !uiState.isUpdating
|
||||||
) {
|
) {
|
||||||
|
|
@ -386,8 +515,39 @@ private fun EmailDetailContent(
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 2.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text("Save Changes")
|
Text(stringResource(R.string.email_detail_save))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun StatusIcon(
|
||||||
|
state: EmailState,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val statusColors = FastMaskStatusColors.current
|
||||||
|
|
||||||
|
val (icon, colorPair) = when (state) {
|
||||||
|
EmailState.ENABLED -> Icons.Default.Check to statusColors.enabled
|
||||||
|
EmailState.DISABLED -> Icons.Default.Close to statusColors.disabled
|
||||||
|
EmailState.DELETED -> Icons.Default.Delete to statusColors.deleted
|
||||||
|
EmailState.PENDING -> Icons.Default.HourglassEmpty to statusColors.pending
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(colorPair.container),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = state.name,
|
||||||
|
modifier = Modifier.size(28.dp),
|
||||||
|
tint = colorPair.content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package com.fastmask.ui.list
|
package com.fastmask.ui.list
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContentScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionScope
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -10,24 +13,27 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.FilterList
|
import androidx.compose.material.icons.filled.FilterList
|
||||||
import androidx.compose.material.icons.filled.Logout
|
|
||||||
import androidx.compose.material.icons.filled.Search
|
import androidx.compose.material.icons.filled.Search
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.FloatingActionButton
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.SearchBar
|
||||||
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
|
@ -35,53 +41,92 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.fastmask.R
|
||||||
import com.fastmask.domain.model.MaskedEmail
|
import com.fastmask.domain.model.MaskedEmail
|
||||||
import com.fastmask.ui.components.ErrorMessage
|
import com.fastmask.ui.components.ErrorMessage
|
||||||
import com.fastmask.ui.components.LoadingIndicator
|
|
||||||
import com.fastmask.ui.components.MaskedEmailCard
|
import com.fastmask.ui.components.MaskedEmailCard
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import com.fastmask.ui.components.ShimmerEmailList
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MaskedEmailListScreen(
|
fun MaskedEmailListScreen(
|
||||||
onNavigateToCreate: () -> Unit,
|
onNavigateToCreate: () -> Unit,
|
||||||
onNavigateToDetail: (String) -> Unit,
|
onNavigateToDetail: (String) -> Unit,
|
||||||
onLogout: () -> Unit,
|
onNavigateToSettings: () -> Unit,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedContentScope: AnimatedContentScope,
|
||||||
viewModel: MaskedEmailListViewModel = hiltViewModel()
|
viewModel: MaskedEmailListViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val uiState by viewModel.uiState.collectAsState()
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
var showFilterMenu by remember { mutableStateOf(false) }
|
var showFilterMenu by remember { mutableStateOf(false) }
|
||||||
|
var searchActive by remember { mutableStateOf(false) }
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
val expandedFab by remember {
|
||||||
viewModel.events.collectLatest { event ->
|
derivedStateOf {
|
||||||
when (event) {
|
listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0
|
||||||
is MaskedEmailListEvent.LoggedOut -> onLogout()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isScrolling by remember {
|
||||||
|
derivedStateOf { listState.isScrollInProgress }
|
||||||
|
}
|
||||||
|
|
||||||
|
val filterEmailsDesc = stringResource(R.string.email_list_filter_emails)
|
||||||
|
val settingsDesc = stringResource(R.string.email_list_settings)
|
||||||
|
val createDesc = stringResource(R.string.email_list_create_description)
|
||||||
|
|
||||||
|
// Refresh the list when the screen resumes (e.g., after creating a new email)
|
||||||
|
// Delay refresh to allow navigation animation to complete smoothly
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
LaunchedEffect(lifecycleOwner) {
|
||||||
|
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
|
delay(250L)
|
||||||
|
viewModel.refreshMaskedEmails()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text("Masked Emails") },
|
title = { Text(stringResource(R.string.email_list_title)) },
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
titleContentColor = MaterialTheme.colorScheme.onSurface
|
||||||
),
|
),
|
||||||
actions = {
|
actions = {
|
||||||
Box {
|
Box {
|
||||||
IconButton(onClick = { showFilterMenu = true }) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
showFilterMenu = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = filterEmailsDesc
|
||||||
|
}
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.FilterList,
|
imageVector = Icons.Default.FilterList,
|
||||||
contentDescription = "Filter"
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
|
|
@ -90,7 +135,16 @@ fun MaskedEmailListScreen(
|
||||||
) {
|
) {
|
||||||
EmailFilter.entries.forEach { filter ->
|
EmailFilter.entries.forEach { filter ->
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(filter.name.lowercase().replaceFirstChar { it.uppercase() }) },
|
text = {
|
||||||
|
Text(
|
||||||
|
when (filter) {
|
||||||
|
EmailFilter.ALL -> stringResource(R.string.filter_all)
|
||||||
|
EmailFilter.ENABLED -> stringResource(R.string.filter_enabled)
|
||||||
|
EmailFilter.DISABLED -> stringResource(R.string.filter_disabled)
|
||||||
|
EmailFilter.DELETED -> stringResource(R.string.filter_deleted)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.onFilterChange(filter)
|
viewModel.onFilterChange(filter)
|
||||||
showFilterMenu = false
|
showFilterMenu = false
|
||||||
|
|
@ -99,22 +153,43 @@ fun MaskedEmailListScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IconButton(onClick = viewModel::logout) {
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onNavigateToSettings()
|
||||||
|
},
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = settingsDesc
|
||||||
|
}
|
||||||
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Logout,
|
imageVector = Icons.Default.Settings,
|
||||||
contentDescription = "Logout"
|
contentDescription = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(onClick = onNavigateToCreate) {
|
ExtendedFloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onNavigateToCreate()
|
||||||
|
},
|
||||||
|
expanded = expandedFab,
|
||||||
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
contentDescription = "Create new masked email"
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
text = { Text(stringResource(R.string.email_list_create_fab)) },
|
||||||
|
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = createDesc
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -122,23 +197,29 @@ fun MaskedEmailListScreen(
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
SearchBar(
|
M3SearchBar(
|
||||||
query = uiState.searchQuery,
|
query = uiState.searchQuery,
|
||||||
onQueryChange = viewModel::onSearchQueryChange,
|
onQueryChange = viewModel::onSearchQueryChange,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
active = searchActive,
|
||||||
|
onActiveChange = { searchActive = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = if (searchActive) 0.dp else 16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (!searchActive) {
|
||||||
FilterChips(
|
FilterChips(
|
||||||
selectedFilter = uiState.selectedFilter,
|
selectedFilter = uiState.selectedFilter,
|
||||||
onFilterSelected = viewModel::onFilterChange,
|
onFilterSelected = viewModel::onFilterChange,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
when {
|
when {
|
||||||
uiState.isLoading && uiState.emails.isEmpty() -> {
|
uiState.isLoading && uiState.emails.isEmpty() -> {
|
||||||
LoadingIndicator()
|
ShimmerEmailList()
|
||||||
}
|
}
|
||||||
|
|
||||||
uiState.error != null && uiState.emails.isEmpty() -> {
|
uiState.error != null && uiState.emails.isEmpty() -> {
|
||||||
|
|
@ -153,7 +234,11 @@ fun MaskedEmailListScreen(
|
||||||
emails = uiState.filteredEmails,
|
emails = uiState.filteredEmails,
|
||||||
isRefreshing = uiState.isLoading,
|
isRefreshing = uiState.isLoading,
|
||||||
onRefresh = viewModel::loadMaskedEmails,
|
onRefresh = viewModel::loadMaskedEmails,
|
||||||
onEmailClick = { email -> onNavigateToDetail(email.id) }
|
onEmailClick = { email -> onNavigateToDetail(email.id) },
|
||||||
|
listState = listState,
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = animatedContentScope,
|
||||||
|
isScrolling = isScrolling
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,26 +246,60 @@ fun MaskedEmailListScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun SearchBar(
|
private fun M3SearchBar(
|
||||||
query: String,
|
query: String,
|
||||||
onQueryChange: (String) -> Unit,
|
onQueryChange: (String) -> Unit,
|
||||||
|
active: Boolean,
|
||||||
|
onActiveChange: (Boolean) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
val haptic = LocalHapticFeedback.current
|
||||||
value = query,
|
val clearSearchDesc = stringResource(R.string.email_list_clear_search)
|
||||||
onValueChange = onQueryChange,
|
|
||||||
modifier = modifier.fillMaxWidth(),
|
SearchBar(
|
||||||
placeholder = { Text("Search emails...") },
|
inputField = {
|
||||||
|
SearchBarDefaults.InputField(
|
||||||
|
query = query,
|
||||||
|
onQueryChange = onQueryChange,
|
||||||
|
onSearch = { onActiveChange(false) },
|
||||||
|
expanded = active,
|
||||||
|
onExpandedChange = onActiveChange,
|
||||||
|
placeholder = { Text(stringResource(R.string.email_list_search_placeholder)) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Search,
|
imageVector = Icons.Default.Search,
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
singleLine = true
|
trailingIcon = {
|
||||||
|
if (query.isNotEmpty()) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onQueryChange("")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Close,
|
||||||
|
contentDescription = clearSearchDesc
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
expanded = active,
|
||||||
|
onExpandedChange = onActiveChange,
|
||||||
|
modifier = modifier,
|
||||||
|
colors = SearchBarDefaults.colors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Search suggestions could be added here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FilterChips(
|
private fun FilterChips(
|
||||||
|
|
@ -188,6 +307,8 @@ private fun FilterChips(
|
||||||
onFilterSelected: (EmailFilter) -> Unit,
|
onFilterSelected: (EmailFilter) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
|
@ -195,20 +316,36 @@ private fun FilterChips(
|
||||||
EmailFilter.entries.forEach { filter ->
|
EmailFilter.entries.forEach { filter ->
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = filter == selectedFilter,
|
selected = filter == selectedFilter,
|
||||||
onClick = { onFilterSelected(filter) },
|
onClick = {
|
||||||
label = { Text(filter.name.lowercase().replaceFirstChar { it.uppercase() }) }
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onFilterSelected(filter)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
when (filter) {
|
||||||
|
EmailFilter.ALL -> stringResource(R.string.filter_all)
|
||||||
|
EmailFilter.ENABLED -> stringResource(R.string.filter_enabled)
|
||||||
|
EmailFilter.DISABLED -> stringResource(R.string.filter_disabled)
|
||||||
|
EmailFilter.DELETED -> stringResource(R.string.filter_deleted)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun EmailList(
|
private fun EmailList(
|
||||||
emails: List<MaskedEmail>,
|
emails: List<MaskedEmail>,
|
||||||
isRefreshing: Boolean,
|
isRefreshing: Boolean,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onEmailClick: (MaskedEmail) -> Unit
|
onEmailClick: (MaskedEmail) -> Unit,
|
||||||
|
listState: LazyListState,
|
||||||
|
sharedTransitionScope: SharedTransitionScope,
|
||||||
|
animatedContentScope: AnimatedContentScope,
|
||||||
|
isScrolling: Boolean
|
||||||
) {
|
) {
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
isRefreshing = isRefreshing,
|
isRefreshing = isRefreshing,
|
||||||
|
|
@ -221,13 +358,14 @@ private fun EmailList(
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "No masked emails found",
|
text = stringResource(R.string.email_list_empty),
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
contentPadding = PaddingValues(16.dp),
|
contentPadding = PaddingValues(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|
@ -237,7 +375,11 @@ private fun EmailList(
|
||||||
) { email ->
|
) { email ->
|
||||||
MaskedEmailCard(
|
MaskedEmailCard(
|
||||||
maskedEmail = email,
|
maskedEmail = email,
|
||||||
onClick = { onEmailClick(email) }
|
onClick = { onEmailClick(email) },
|
||||||
|
sharedTransitionScope = sharedTransitionScope,
|
||||||
|
animatedContentScope = animatedContentScope,
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
isScrolling = isScrolling
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,44 @@ class MaskedEmailListViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refreshMaskedEmails() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Don't show loading if we already have data (soft refresh)
|
||||||
|
if (_uiState.value.emails.isEmpty()) {
|
||||||
|
_uiState.update { it.copy(isLoading = true, error = null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaskedEmailsUseCase().fold(
|
||||||
|
onSuccess = { emails ->
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
emails = emails.sortedByDescending { email -> email.createdAt },
|
||||||
|
filteredEmails = filterEmails(
|
||||||
|
emails,
|
||||||
|
it.searchQuery,
|
||||||
|
it.selectedFilter
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { error ->
|
||||||
|
// Only show error if we have no data
|
||||||
|
if (_uiState.value.emails.isEmpty()) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
error = error.message ?: "Failed to load emails"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_uiState.update { it.copy(isLoading = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onSearchQueryChange(query: String) {
|
fun onSearchQueryChange(query: String) {
|
||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
package com.fastmask.ui.navigation
|
package com.fastmask.ui.navigation
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.SharedTransitionLayout
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
|
@ -11,19 +18,52 @@ import com.fastmask.ui.auth.LoginScreen
|
||||||
import com.fastmask.ui.create.CreateMaskedEmailScreen
|
import com.fastmask.ui.create.CreateMaskedEmailScreen
|
||||||
import com.fastmask.ui.detail.MaskedEmailDetailScreen
|
import com.fastmask.ui.detail.MaskedEmailDetailScreen
|
||||||
import com.fastmask.ui.list.MaskedEmailListScreen
|
import com.fastmask.ui.list.MaskedEmailListScreen
|
||||||
|
import com.fastmask.ui.settings.SettingsScreen
|
||||||
|
|
||||||
|
private const val TRANSITION_DURATION_MS = 220
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun FastMaskNavHost(
|
fun FastMaskNavHost(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
startDestination: String,
|
startDestination: String,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
SharedTransitionLayout {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = startDestination,
|
startDestination = startDestination,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
enterTransition = {
|
||||||
|
slideIntoContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||||
|
animationSpec = tween(TRANSITION_DURATION_MS, easing = FastOutSlowInEasing)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
exitTransition = {
|
||||||
|
slideOutOfContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||||
|
animationSpec = tween(TRANSITION_DURATION_MS, easing = FastOutSlowInEasing)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
popEnterTransition = {
|
||||||
|
slideIntoContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.End,
|
||||||
|
animationSpec = tween(TRANSITION_DURATION_MS, easing = FastOutSlowInEasing)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
popExitTransition = {
|
||||||
|
slideOutOfContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.End,
|
||||||
|
animationSpec = tween(TRANSITION_DURATION_MS, easing = FastOutSlowInEasing)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
composable(
|
||||||
|
route = NavRoutes.LOGIN,
|
||||||
|
enterTransition = { fadeIn(animationSpec = tween(TRANSITION_DURATION_MS)) },
|
||||||
|
exitTransition = { fadeOut(animationSpec = tween(TRANSITION_DURATION_MS)) }
|
||||||
) {
|
) {
|
||||||
composable(NavRoutes.LOGIN) {
|
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
onLoginSuccess = {
|
onLoginSuccess = {
|
||||||
navController.navigate(NavRoutes.EMAIL_LIST) {
|
navController.navigate(NavRoutes.EMAIL_LIST) {
|
||||||
|
|
@ -41,6 +81,19 @@ fun FastMaskNavHost(
|
||||||
onNavigateToDetail = { emailId ->
|
onNavigateToDetail = { emailId ->
|
||||||
navController.navigate(NavRoutes.emailDetail(emailId))
|
navController.navigate(NavRoutes.emailDetail(emailId))
|
||||||
},
|
},
|
||||||
|
onNavigateToSettings = {
|
||||||
|
navController.navigate(NavRoutes.SETTINGS)
|
||||||
|
},
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedContentScope = this@composable
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
composable(NavRoutes.SETTINGS) {
|
||||||
|
SettingsScreen(
|
||||||
|
onNavigateBack = {
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
onLogout = {
|
onLogout = {
|
||||||
navController.navigate(NavRoutes.LOGIN) {
|
navController.navigate(NavRoutes.LOGIN) {
|
||||||
popUpTo(0) { inclusive = true }
|
popUpTo(0) { inclusive = true }
|
||||||
|
|
@ -66,8 +119,11 @@ fun FastMaskNavHost(
|
||||||
MaskedEmailDetailScreen(
|
MaskedEmailDetailScreen(
|
||||||
onNavigateBack = {
|
onNavigateBack = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
}
|
},
|
||||||
|
sharedTransitionScope = this@SharedTransitionLayout,
|
||||||
|
animatedContentScope = this@composable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ object NavRoutes {
|
||||||
const val EMAIL_LIST = "email_list"
|
const val EMAIL_LIST = "email_list"
|
||||||
const val CREATE_EMAIL = "create_email"
|
const val CREATE_EMAIL = "create_email"
|
||||||
const val EMAIL_DETAIL = "email_detail/{emailId}"
|
const val EMAIL_DETAIL = "email_detail/{emailId}"
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
|
||||||
fun emailDetail(emailId: String) = "email_detail/$emailId"
|
fun emailDetail(emailId: String) = "email_detail/$emailId"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
304
app/src/main/java/com/fastmask/ui/settings/SettingsScreen.kt
Normal file
304
app/src/main/java/com/fastmask/ui/settings/SettingsScreen.kt
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
package com.fastmask.ui.settings
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.selection.selectable
|
||||||
|
import androidx.compose.foundation.selection.selectableGroup
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||||
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
|
import androidx.compose.material.icons.filled.Email
|
||||||
|
import androidx.compose.material.icons.filled.Language
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.fastmask.BuildConfig
|
||||||
|
import com.fastmask.R
|
||||||
|
import com.fastmask.domain.model.Language
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsScreen(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
onLogout: () -> Unit,
|
||||||
|
viewModel: SettingsViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
var showLanguageDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.events.collectLatest { event ->
|
||||||
|
when (event) {
|
||||||
|
is SettingsEvent.LoggedOut -> onLogout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLanguageDialog) {
|
||||||
|
LanguagePickerDialog(
|
||||||
|
selectedLanguage = uiState.selectedLanguage,
|
||||||
|
onLanguageSelected = { language ->
|
||||||
|
viewModel.onLanguageSelected(language)
|
||||||
|
showLanguageDialog = false
|
||||||
|
},
|
||||||
|
onDismiss = { showLanguageDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(stringResource(R.string.settings_title)) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onNavigateBack()
|
||||||
|
},
|
||||||
|
modifier = Modifier.semantics {
|
||||||
|
contentDescription = "Navigate back"
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
// Language Setting
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(stringResource(R.string.settings_language)) },
|
||||||
|
supportingContent = {
|
||||||
|
val languageName = uiState.selectedLanguage?.let {
|
||||||
|
stringResource(it.displayNameRes)
|
||||||
|
} ?: stringResource(R.string.settings_system_default)
|
||||||
|
Text(languageName)
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Language,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ChevronRight,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
showLanguageDialog = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
// Contact & Feedback
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(stringResource(R.string.settings_contact)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.settings_contact_description)) },
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Email,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
trailingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ChevronRight,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
val emailIntent = Intent(Intent.ACTION_SENDTO).apply {
|
||||||
|
data = Uri.parse("mailto:")
|
||||||
|
putExtra(Intent.EXTRA_EMAIL, arrayOf("pawel@orzech.me"))
|
||||||
|
putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.settings_feedback_subject))
|
||||||
|
}
|
||||||
|
if (emailIntent.resolveActivity(context.packageManager) != null) {
|
||||||
|
context.startActivity(emailIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(stringResource(R.string.settings_logout)) },
|
||||||
|
supportingContent = { Text(stringResource(R.string.settings_logout_description)) },
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.Logout,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
viewModel.logout()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
// Version
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings_version, BuildConfig.VERSION_NAME),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LanguagePickerDialog(
|
||||||
|
selectedLanguage: Language?,
|
||||||
|
onLanguageSelected: (Language?) -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
val haptic = LocalHapticFeedback.current
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(stringResource(R.string.settings_select_language)) },
|
||||||
|
text = {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.selectableGroup()
|
||||||
|
) {
|
||||||
|
// System Default option
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.selectable(
|
||||||
|
selected = selectedLanguage == null,
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onLanguageSelected(null)
|
||||||
|
},
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
.padding(vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedLanguage == null,
|
||||||
|
onClick = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.settings_system_default),
|
||||||
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(Language.entries) { language ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.selectable(
|
||||||
|
selected = selectedLanguage == language,
|
||||||
|
onClick = {
|
||||||
|
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
|
onLanguageSelected(language)
|
||||||
|
},
|
||||||
|
role = Role.RadioButton
|
||||||
|
)
|
||||||
|
.padding(vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedLanguage == language,
|
||||||
|
onClick = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(language.displayNameRes),
|
||||||
|
modifier = Modifier.padding(start = 16.dp),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.email_detail_delete_cancel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.fastmask.ui.settings
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.fastmask.domain.model.Language
|
||||||
|
import com.fastmask.domain.usecase.GetCurrentLanguageUseCase
|
||||||
|
import com.fastmask.domain.usecase.LogoutUseCase
|
||||||
|
import com.fastmask.domain.usecase.SetLanguageUseCase
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SettingsViewModel @Inject constructor(
|
||||||
|
private val getCurrentLanguageUseCase: GetCurrentLanguageUseCase,
|
||||||
|
private val setLanguageUseCase: SetLanguageUseCase,
|
||||||
|
private val logoutUseCase: LogoutUseCase
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(SettingsUiState())
|
||||||
|
val uiState: StateFlow<SettingsUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
private val _events = MutableSharedFlow<SettingsEvent>()
|
||||||
|
val events: SharedFlow<SettingsEvent> = _events.asSharedFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadCurrentLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadCurrentLanguage() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
getCurrentLanguageUseCase().collect { language ->
|
||||||
|
_uiState.update { it.copy(selectedLanguage = language) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onLanguageSelected(language: Language?) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
setLanguageUseCase(language)
|
||||||
|
_uiState.update { it.copy(selectedLanguage = language) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
logoutUseCase()
|
||||||
|
viewModelScope.launch {
|
||||||
|
_events.emit(SettingsEvent.LoggedOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SettingsUiState(
|
||||||
|
val selectedLanguage: Language? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed class SettingsEvent {
|
||||||
|
data object LoggedOut : SettingsEvent()
|
||||||
|
}
|
||||||
|
|
@ -2,19 +2,103 @@ package com.fastmask.ui.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
// Fastmail Brand Colors
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
|
||||||
val Pink40 = Color(0xFF7D5260)
|
|
||||||
|
|
||||||
val FastmailBlue = Color(0xFF0066CC)
|
val FastmailBlue = Color(0xFF0066CC)
|
||||||
val FastmailBlueLight = Color(0xFF4D94DB)
|
val FastmailBlueLight = Color(0xFF4D94DB)
|
||||||
val FastmailBlueDark = Color(0xFF004C99)
|
val FastmailBlueDark = Color(0xFF004C99)
|
||||||
|
|
||||||
val EnabledGreen = Color(0xFF4CAF50)
|
// M3 Light Theme Colors
|
||||||
val DisabledGray = Color(0xFF9E9E9E)
|
val md_theme_light_primary = Color(0xFF0061A4)
|
||||||
val DeletedRed = Color(0xFFE53935)
|
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||||
val PendingOrange = Color(0xFFFF9800)
|
val md_theme_light_primaryContainer = Color(0xFFD1E4FF)
|
||||||
|
val md_theme_light_onPrimaryContainer = Color(0xFF001D36)
|
||||||
|
val md_theme_light_secondary = Color(0xFF535F70)
|
||||||
|
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_secondaryContainer = Color(0xFFD7E3F7)
|
||||||
|
val md_theme_light_onSecondaryContainer = Color(0xFF101C2B)
|
||||||
|
val md_theme_light_tertiary = Color(0xFF6B5778)
|
||||||
|
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_tertiaryContainer = Color(0xFFF2DAFF)
|
||||||
|
val md_theme_light_onTertiaryContainer = Color(0xFF251431)
|
||||||
|
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||||
|
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||||
|
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||||
|
val md_theme_light_background = Color(0xFFFDFCFF)
|
||||||
|
val md_theme_light_onBackground = Color(0xFF1A1C1E)
|
||||||
|
val md_theme_light_surface = Color(0xFFFDFCFF)
|
||||||
|
val md_theme_light_onSurface = Color(0xFF1A1C1E)
|
||||||
|
val md_theme_light_surfaceVariant = Color(0xFFDFE2EB)
|
||||||
|
val md_theme_light_onSurfaceVariant = Color(0xFF43474E)
|
||||||
|
val md_theme_light_outline = Color(0xFF73777F)
|
||||||
|
val md_theme_light_outlineVariant = Color(0xFFC3C7CF)
|
||||||
|
val md_theme_light_inverseOnSurface = Color(0xFFF1F0F4)
|
||||||
|
val md_theme_light_inverseSurface = Color(0xFF2F3033)
|
||||||
|
val md_theme_light_inversePrimary = Color(0xFF9ECAFF)
|
||||||
|
val md_theme_light_surfaceTint = Color(0xFF0061A4)
|
||||||
|
val md_theme_light_scrim = Color(0xFF000000)
|
||||||
|
|
||||||
|
// M3 Surface Containers (Light)
|
||||||
|
val md_theme_light_surfaceContainerLowest = Color(0xFFFFFFFF)
|
||||||
|
val md_theme_light_surfaceContainerLow = Color(0xFFF7F6FA)
|
||||||
|
val md_theme_light_surfaceContainer = Color(0xFFF1F0F4)
|
||||||
|
val md_theme_light_surfaceContainerHigh = Color(0xFFEBEBEF)
|
||||||
|
val md_theme_light_surfaceContainerHighest = Color(0xFFE6E5E9)
|
||||||
|
|
||||||
|
// M3 Dark Theme Colors
|
||||||
|
val md_theme_dark_primary = Color(0xFF9ECAFF)
|
||||||
|
val md_theme_dark_onPrimary = Color(0xFF003258)
|
||||||
|
val md_theme_dark_primaryContainer = Color(0xFF00497D)
|
||||||
|
val md_theme_dark_onPrimaryContainer = Color(0xFFD1E4FF)
|
||||||
|
val md_theme_dark_secondary = Color(0xFFBBC7DB)
|
||||||
|
val md_theme_dark_onSecondary = Color(0xFF253140)
|
||||||
|
val md_theme_dark_secondaryContainer = Color(0xFF3B4858)
|
||||||
|
val md_theme_dark_onSecondaryContainer = Color(0xFFD7E3F7)
|
||||||
|
val md_theme_dark_tertiary = Color(0xFFD6BEE4)
|
||||||
|
val md_theme_dark_onTertiary = Color(0xFF3B2948)
|
||||||
|
val md_theme_dark_tertiaryContainer = Color(0xFF523F5F)
|
||||||
|
val md_theme_dark_onTertiaryContainer = Color(0xFFF2DAFF)
|
||||||
|
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||||
|
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||||
|
val md_theme_dark_onError = Color(0xFF690005)
|
||||||
|
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||||
|
val md_theme_dark_background = Color(0xFF1A1C1E)
|
||||||
|
val md_theme_dark_onBackground = Color(0xFFE2E2E6)
|
||||||
|
val md_theme_dark_surface = Color(0xFF1A1C1E)
|
||||||
|
val md_theme_dark_onSurface = Color(0xFFE2E2E6)
|
||||||
|
val md_theme_dark_surfaceVariant = Color(0xFF43474E)
|
||||||
|
val md_theme_dark_onSurfaceVariant = Color(0xFFC3C7CF)
|
||||||
|
val md_theme_dark_outline = Color(0xFF8D9199)
|
||||||
|
val md_theme_dark_outlineVariant = Color(0xFF43474E)
|
||||||
|
val md_theme_dark_inverseOnSurface = Color(0xFF1A1C1E)
|
||||||
|
val md_theme_dark_inverseSurface = Color(0xFFE2E2E6)
|
||||||
|
val md_theme_dark_inversePrimary = Color(0xFF0061A4)
|
||||||
|
val md_theme_dark_surfaceTint = Color(0xFF9ECAFF)
|
||||||
|
val md_theme_dark_scrim = Color(0xFF000000)
|
||||||
|
|
||||||
|
// M3 Surface Containers (Dark)
|
||||||
|
val md_theme_dark_surfaceContainerLowest = Color(0xFF0F1113)
|
||||||
|
val md_theme_dark_surfaceContainerLow = Color(0xFF1A1C1E)
|
||||||
|
val md_theme_dark_surfaceContainer = Color(0xFF1E2022)
|
||||||
|
val md_theme_dark_surfaceContainerHigh = Color(0xFF282A2D)
|
||||||
|
val md_theme_dark_surfaceContainerHighest = Color(0xFF333537)
|
||||||
|
|
||||||
|
// Semantic Status Colors - Light Theme (Tonal Containers)
|
||||||
|
val StatusEnabledContainerLight = Color(0xFFD4F5D0)
|
||||||
|
val StatusEnabledContentLight = Color(0xFF0D5F0D)
|
||||||
|
val StatusDisabledContainerLight = Color(0xFFE0E0E0)
|
||||||
|
val StatusDisabledContentLight = Color(0xFF5C5C5C)
|
||||||
|
val StatusDeletedContainerLight = Color(0xFFFFDAD6)
|
||||||
|
val StatusDeletedContentLight = Color(0xFFBA1A1A)
|
||||||
|
val StatusPendingContainerLight = Color(0xFFFFE0B2)
|
||||||
|
val StatusPendingContentLight = Color(0xFFB36B00)
|
||||||
|
|
||||||
|
// Semantic Status Colors - Dark Theme (Tonal Containers)
|
||||||
|
val StatusEnabledContainerDark = Color(0xFF1E4D1E)
|
||||||
|
val StatusEnabledContentDark = Color(0xFF90EE90)
|
||||||
|
val StatusDisabledContainerDark = Color(0xFF3D3D3D)
|
||||||
|
val StatusDisabledContentDark = Color(0xFFB0B0B0)
|
||||||
|
val StatusDeletedContainerDark = Color(0xFF93000A)
|
||||||
|
val StatusDeletedContentDark = Color(0xFFFFB4AB)
|
||||||
|
val StatusPendingContainerDark = Color(0xFF7A4700)
|
||||||
|
val StatusPendingContentDark = Color(0xFFFFCC80)
|
||||||
|
|
|
||||||
66
app/src/main/java/com/fastmask/ui/theme/StatusColors.kt
Normal file
66
app/src/main/java/com/fastmask/ui/theme/StatusColors.kt
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.fastmask.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class StatusColorPair(
|
||||||
|
val container: Color,
|
||||||
|
val content: Color
|
||||||
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class StatusColors(
|
||||||
|
val enabled: StatusColorPair,
|
||||||
|
val disabled: StatusColorPair,
|
||||||
|
val deleted: StatusColorPair,
|
||||||
|
val pending: StatusColorPair
|
||||||
|
)
|
||||||
|
|
||||||
|
val LightStatusColors = StatusColors(
|
||||||
|
enabled = StatusColorPair(
|
||||||
|
container = StatusEnabledContainerLight,
|
||||||
|
content = StatusEnabledContentLight
|
||||||
|
),
|
||||||
|
disabled = StatusColorPair(
|
||||||
|
container = StatusDisabledContainerLight,
|
||||||
|
content = StatusDisabledContentLight
|
||||||
|
),
|
||||||
|
deleted = StatusColorPair(
|
||||||
|
container = StatusDeletedContainerLight,
|
||||||
|
content = StatusDeletedContentLight
|
||||||
|
),
|
||||||
|
pending = StatusColorPair(
|
||||||
|
container = StatusPendingContainerLight,
|
||||||
|
content = StatusPendingContentLight
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val DarkStatusColors = StatusColors(
|
||||||
|
enabled = StatusColorPair(
|
||||||
|
container = StatusEnabledContainerDark,
|
||||||
|
content = StatusEnabledContentDark
|
||||||
|
),
|
||||||
|
disabled = StatusColorPair(
|
||||||
|
container = StatusDisabledContainerDark,
|
||||||
|
content = StatusDisabledContentDark
|
||||||
|
),
|
||||||
|
deleted = StatusColorPair(
|
||||||
|
container = StatusDeletedContainerDark,
|
||||||
|
content = StatusDeletedContentDark
|
||||||
|
),
|
||||||
|
pending = StatusColorPair(
|
||||||
|
container = StatusPendingContainerDark,
|
||||||
|
content = StatusPendingContentDark
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val LocalStatusColors = staticCompositionLocalOf { LightStatusColors }
|
||||||
|
|
||||||
|
object FastMaskStatusColors {
|
||||||
|
val current: StatusColors
|
||||||
|
@Composable
|
||||||
|
get() = LocalStatusColors.current
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.fastmask.ui.theme
|
package com.fastmask.ui.theme
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -9,37 +8,81 @@ import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.toArgb
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
primary = FastmailBlueLight,
|
primary = md_theme_dark_primary,
|
||||||
secondary = PurpleGrey80,
|
onPrimary = md_theme_dark_onPrimary,
|
||||||
tertiary = Pink80,
|
primaryContainer = md_theme_dark_primaryContainer,
|
||||||
background = Color(0xFF121212),
|
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||||
surface = Color(0xFF1E1E1E),
|
secondary = md_theme_dark_secondary,
|
||||||
onPrimary = Color.White,
|
onSecondary = md_theme_dark_onSecondary,
|
||||||
onSecondary = Color.White,
|
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||||
onTertiary = Color.White,
|
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||||
onBackground = Color.White,
|
tertiary = md_theme_dark_tertiary,
|
||||||
onSurface = Color.White
|
onTertiary = md_theme_dark_onTertiary,
|
||||||
|
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||||
|
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||||
|
error = md_theme_dark_error,
|
||||||
|
errorContainer = md_theme_dark_errorContainer,
|
||||||
|
onError = md_theme_dark_onError,
|
||||||
|
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||||
|
background = md_theme_dark_background,
|
||||||
|
onBackground = md_theme_dark_onBackground,
|
||||||
|
surface = md_theme_dark_surface,
|
||||||
|
onSurface = md_theme_dark_onSurface,
|
||||||
|
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||||
|
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||||
|
outline = md_theme_dark_outline,
|
||||||
|
outlineVariant = md_theme_dark_outlineVariant,
|
||||||
|
inverseOnSurface = md_theme_dark_inverseOnSurface,
|
||||||
|
inverseSurface = md_theme_dark_inverseSurface,
|
||||||
|
inversePrimary = md_theme_dark_inversePrimary,
|
||||||
|
surfaceTint = md_theme_dark_surfaceTint,
|
||||||
|
scrim = md_theme_dark_scrim,
|
||||||
|
surfaceContainerLowest = md_theme_dark_surfaceContainerLowest,
|
||||||
|
surfaceContainerLow = md_theme_dark_surfaceContainerLow,
|
||||||
|
surfaceContainer = md_theme_dark_surfaceContainer,
|
||||||
|
surfaceContainerHigh = md_theme_dark_surfaceContainerHigh,
|
||||||
|
surfaceContainerHighest = md_theme_dark_surfaceContainerHighest
|
||||||
)
|
)
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = FastmailBlue,
|
primary = md_theme_light_primary,
|
||||||
secondary = PurpleGrey40,
|
onPrimary = md_theme_light_onPrimary,
|
||||||
tertiary = Pink40,
|
primaryContainer = md_theme_light_primaryContainer,
|
||||||
background = Color(0xFFFFFBFE),
|
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||||
surface = Color(0xFFFFFBFE),
|
secondary = md_theme_light_secondary,
|
||||||
onPrimary = Color.White,
|
onSecondary = md_theme_light_onSecondary,
|
||||||
onSecondary = Color.White,
|
secondaryContainer = md_theme_light_secondaryContainer,
|
||||||
onTertiary = Color.White,
|
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||||
onBackground = Color(0xFF1C1B1F),
|
tertiary = md_theme_light_tertiary,
|
||||||
onSurface = Color(0xFF1C1B1F)
|
onTertiary = md_theme_light_onTertiary,
|
||||||
|
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||||
|
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||||
|
error = md_theme_light_error,
|
||||||
|
errorContainer = md_theme_light_errorContainer,
|
||||||
|
onError = md_theme_light_onError,
|
||||||
|
onErrorContainer = md_theme_light_onErrorContainer,
|
||||||
|
background = md_theme_light_background,
|
||||||
|
onBackground = md_theme_light_onBackground,
|
||||||
|
surface = md_theme_light_surface,
|
||||||
|
onSurface = md_theme_light_onSurface,
|
||||||
|
surfaceVariant = md_theme_light_surfaceVariant,
|
||||||
|
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||||
|
outline = md_theme_light_outline,
|
||||||
|
outlineVariant = md_theme_light_outlineVariant,
|
||||||
|
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||||
|
inverseSurface = md_theme_light_inverseSurface,
|
||||||
|
inversePrimary = md_theme_light_inversePrimary,
|
||||||
|
surfaceTint = md_theme_light_surfaceTint,
|
||||||
|
scrim = md_theme_light_scrim,
|
||||||
|
surfaceContainerLowest = md_theme_light_surfaceContainerLowest,
|
||||||
|
surfaceContainerLow = md_theme_light_surfaceContainerLow,
|
||||||
|
surfaceContainer = md_theme_light_surfaceContainer,
|
||||||
|
surfaceContainerHigh = md_theme_light_surfaceContainerHigh,
|
||||||
|
surfaceContainerHighest = md_theme_light_surfaceContainerHighest
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -57,18 +100,15 @@ fun FastMaskTheme(
|
||||||
else -> LightColorScheme
|
else -> LightColorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
val view = LocalView.current
|
val statusColors = if (darkTheme) DarkStatusColors else LightStatusColors
|
||||||
if (!view.isInEditMode) {
|
|
||||||
SideEffect {
|
|
||||||
val window = (view.context as Activity).window
|
|
||||||
window.statusBarColor = colorScheme.surface.toArgb()
|
|
||||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalStatusColors provides statusColors
|
||||||
|
) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,67 +4,126 @@ import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.googlefonts.Font
|
||||||
|
import androidx.compose.ui.text.googlefonts.GoogleFont
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
val provider = GoogleFont.Provider(
|
||||||
|
providerAuthority = "com.google.android.gms.fonts",
|
||||||
|
providerPackage = "com.google.android.gms",
|
||||||
|
certificates = com.fastmask.R.array.com_google_android_gms_fonts_certs
|
||||||
|
)
|
||||||
|
|
||||||
|
val InterFont = GoogleFont("Inter")
|
||||||
|
|
||||||
|
val InterFontFamily = FontFamily(
|
||||||
|
Font(googleFont = InterFont, fontProvider = provider, weight = FontWeight.Normal),
|
||||||
|
Font(googleFont = InterFont, fontProvider = provider, weight = FontWeight.Medium),
|
||||||
|
Font(googleFont = InterFont, fontProvider = provider, weight = FontWeight.SemiBold),
|
||||||
|
Font(googleFont = InterFont, fontProvider = provider, weight = FontWeight.Bold)
|
||||||
|
)
|
||||||
|
|
||||||
val Typography = Typography(
|
val Typography = Typography(
|
||||||
bodyLarge = TextStyle(
|
displayLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp,
|
fontSize = 57.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 64.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = (-0.25).sp
|
||||||
),
|
),
|
||||||
bodyMedium = TextStyle(
|
displayMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 14.sp,
|
fontSize = 45.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 52.sp,
|
||||||
letterSpacing = 0.25.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
bodySmall = TextStyle(
|
displaySmall = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 12.sp,
|
fontSize = 36.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 44.sp,
|
||||||
letterSpacing = 0.4.sp
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
headlineLarge = TextStyle(
|
||||||
|
fontFamily = InterFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 32.sp,
|
||||||
|
lineHeight = 40.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
headlineMedium = TextStyle(
|
||||||
|
fontFamily = InterFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 28.sp,
|
||||||
|
lineHeight = 36.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
|
),
|
||||||
|
headlineSmall = TextStyle(
|
||||||
|
fontFamily = InterFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
lineHeight = 32.sp,
|
||||||
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
titleLarge = TextStyle(
|
titleLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
lineHeight = 28.sp,
|
lineHeight = 28.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
titleMedium = TextStyle(
|
titleMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.15.sp
|
letterSpacing = 0.15.sp
|
||||||
),
|
),
|
||||||
titleSmall = TextStyle(
|
titleSmall = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 20.sp,
|
||||||
letterSpacing = 0.1.sp
|
letterSpacing = 0.1.sp
|
||||||
),
|
),
|
||||||
|
bodyLarge = TextStyle(
|
||||||
|
fontFamily = InterFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 24.sp,
|
||||||
|
letterSpacing = 0.5.sp
|
||||||
|
),
|
||||||
|
bodyMedium = TextStyle(
|
||||||
|
fontFamily = InterFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
letterSpacing = 0.25.sp
|
||||||
|
),
|
||||||
|
bodySmall = TextStyle(
|
||||||
|
fontFamily = InterFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.4.sp
|
||||||
|
),
|
||||||
labelLarge = TextStyle(
|
labelLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
lineHeight = 20.sp,
|
lineHeight = 20.sp,
|
||||||
letterSpacing = 0.1.sp
|
letterSpacing = 0.1.sp
|
||||||
),
|
),
|
||||||
labelMedium = TextStyle(
|
labelMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
),
|
),
|
||||||
labelSmall = TextStyle(
|
labelSmall = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = InterFontFamily,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
|
|
|
||||||
24
app/src/main/res/drawable/ic_launcher_monochrome.xml
Normal file
24
app/src/main/res/drawable/ic_launcher_monochrome.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.35"
|
||||||
|
android:scaleY="0.35"
|
||||||
|
android:translateX="35"
|
||||||
|
android:translateY="35">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M54,10C29.7,10 10,29.7 10,54s19.7,44 44,44s44,-19.7 44,-44S78.3,10 54,10zM54,90c-19.9,0 -36,-16.1 -36,-36s16.1,-36 36,-36s36,16.1 36,36S73.9,90 54,90z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M54,30c-13.3,0 -24,10.7 -24,24s10.7,24 24,24s24,-10.7 24,-24S67.3,30 54,30zM54,70c-8.8,0 -16,-7.2 -16,-16s7.2,-16 16,-16s16,7.2 16,16S62.8,70 54,70z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M75,40l-42,0l0,8l42,0z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M75,60l-42,0l0,8l42,0z"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|
|
||||||
121
app/src/main/res/values-ar/strings.xml
Normal file
121
app/src/main/res/values-ar/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">مدير البريد المقنّع من Fastmail</string>
|
||||||
|
<string name="login_api_token_label">رمز API</string>
|
||||||
|
<string name="login_api_token_placeholder">أدخل رمز API الخاص بـ Fastmail</string>
|
||||||
|
<string name="login_button">تسجيل الدخول</string>
|
||||||
|
<string name="login_show_token">إظهار الرمز</string>
|
||||||
|
<string name="login_hide_token">إخفاء الرمز</string>
|
||||||
|
<string name="login_instructions_title">كيفية الحصول على رمز API:</string>
|
||||||
|
<string name="login_instructions_step1">١. سجّل الدخول إلى تطبيق Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">٢. انتقل إلى الإعدادات > الخصوصية والأمان</string>
|
||||||
|
<string name="login_instructions_step3">٣. انقر على التكاملات > رموز API</string>
|
||||||
|
<string name="login_instructions_step4">٤. أنشئ رمزاً جديداً بصلاحية \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">٥. انسخ الرمز والصقه أعلاه</string>
|
||||||
|
<string name="login_instructions_full">١. سجّل الدخول إلى تطبيق Fastmail\n٢. انتقل إلى الإعدادات > الخصوصية والأمان\n٣. انقر على التكاملات > رموز API\n٤. أنشئ رمزاً جديداً بصلاحية \"Masked Email\"\n٥. انسخ الرمز والصقه أعلاه</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">البريد المقنّع</string>
|
||||||
|
<string name="email_list_search_placeholder">البحث في البريد…</string>
|
||||||
|
<string name="email_list_clear_search">مسح البحث</string>
|
||||||
|
<string name="email_list_filter_emails">تصفية البريد</string>
|
||||||
|
<string name="email_list_create_fab">إنشاء</string>
|
||||||
|
<string name="email_list_create_description">إنشاء بريد مقنّع جديد</string>
|
||||||
|
<string name="email_list_empty">لم يتم العثور على بريد مقنّع</string>
|
||||||
|
<string name="email_list_logout">تسجيل الخروج</string>
|
||||||
|
<string name="email_list_settings">الإعدادات</string>
|
||||||
|
<string name="filter_all">الكل</string>
|
||||||
|
<string name="filter_enabled">مُفعّل</string>
|
||||||
|
<string name="filter_disabled">معطّل</string>
|
||||||
|
<string name="filter_deleted">محذوف</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">إنشاء بريد مقنّع</string>
|
||||||
|
<string name="create_email_prefix_label">بادئة البريد (اختياري)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">مثال: mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">الحد الأقصى ٦٤ حرفاً: أحرف صغيرة، أرقام، شرطات سفلية</string>
|
||||||
|
<string name="create_email_domain_label">النطاق المرتبط (اختياري)</string>
|
||||||
|
<string name="create_email_domain_placeholder">مثال: example.com</string>
|
||||||
|
<string name="create_email_description_label">الوصف (اختياري)</string>
|
||||||
|
<string name="create_email_description_placeholder">مثال: حساب التسوق</string>
|
||||||
|
<string name="create_email_url_label">الرابط (اختياري)</string>
|
||||||
|
<string name="create_email_url_placeholder">مثال: https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">الحالة الأولية</string>
|
||||||
|
<string name="create_email_button">إنشاء بريد مقنّع</string>
|
||||||
|
<string name="create_email_created">تم الإنشاء: %s</string>
|
||||||
|
<string name="create_email_copy_action">نسخ</string>
|
||||||
|
<string name="navigate_back">رجوع</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">تفاصيل البريد</string>
|
||||||
|
<string name="email_detail_copy_email">نسخ عنوان البريد</string>
|
||||||
|
<string name="email_detail_delete">حذف البريد</string>
|
||||||
|
<string name="email_detail_status">الحالة:</string>
|
||||||
|
<string name="email_detail_created_by">أنشأه: %s</string>
|
||||||
|
<string name="email_detail_created">تاريخ الإنشاء: %s</string>
|
||||||
|
<string name="email_detail_last_message">آخر رسالة: %s</string>
|
||||||
|
<string name="email_detail_enable">تفعيل</string>
|
||||||
|
<string name="email_detail_disable">تعطيل</string>
|
||||||
|
<string name="email_detail_edit_title">تعديل التفاصيل</string>
|
||||||
|
<string name="email_detail_description_label">الوصف</string>
|
||||||
|
<string name="email_detail_domain_label">النطاق المرتبط</string>
|
||||||
|
<string name="email_detail_url_label">الرابط</string>
|
||||||
|
<string name="email_detail_save">حفظ التغييرات</string>
|
||||||
|
<string name="email_detail_updated">تم التحديث بنجاح</string>
|
||||||
|
<string name="email_detail_deleted">تم الحذف بنجاح</string>
|
||||||
|
<string name="email_detail_copied">تم النسخ إلى الحافظة</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">حذف البريد المقنّع</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">هل أنت متأكد أنك تريد حذف هذا البريد المقنّع؟ لا يمكن التراجع عن هذا الإجراء.</string>
|
||||||
|
<string name="email_detail_delete_confirm">حذف</string>
|
||||||
|
<string name="email_detail_delete_cancel">إلغاء</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">مُفعّل</string>
|
||||||
|
<string name="state_disabled">معطّل</string>
|
||||||
|
<string name="state_deleted">محذوف</string>
|
||||||
|
<string name="state_pending">قيد الانتظار</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">فشل تحميل البريد</string>
|
||||||
|
<string name="error_retry">إعادة المحاولة</string>
|
||||||
|
<string name="error_generic">حدث خطأ</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">الإعدادات</string>
|
||||||
|
<string name="settings_language">اللغة</string>
|
||||||
|
<string name="settings_language_description">اختر لغة التطبيق</string>
|
||||||
|
<string name="settings_system_default">الافتراضي للنظام</string>
|
||||||
|
<string name="settings_contact">التواصل والملاحظات</string>
|
||||||
|
<string name="settings_contact_description">أرسل لنا ملاحظاتك</string>
|
||||||
|
<string name="settings_logout">تسجيل الخروج</string>
|
||||||
|
<string name="settings_logout_description">الخروج من حسابك</string>
|
||||||
|
<string name="settings_version">الإصدار %s</string>
|
||||||
|
<string name="settings_select_language">اختر اللغة</string>
|
||||||
|
<string name="settings_feedback_subject">ملاحظات FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-bn/strings.xml
Normal file
121
app/src/main/res/values-bn/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail মাস্কড ইমেইল ম্যানেজার</string>
|
||||||
|
<string name="login_api_token_label">API টোকেন</string>
|
||||||
|
<string name="login_api_token_placeholder">আপনার Fastmail API টোকেন দিন</string>
|
||||||
|
<string name="login_button">লগইন</string>
|
||||||
|
<string name="login_show_token">টোকেন দেখান</string>
|
||||||
|
<string name="login_hide_token">টোকেন লুকান</string>
|
||||||
|
<string name="login_instructions_title">কীভাবে আপনার API টোকেন পাবেন:</string>
|
||||||
|
<string name="login_instructions_step1">১. Fastmail ওয়েব অ্যাপে লগইন করুন</string>
|
||||||
|
<string name="login_instructions_step2">২. সেটিংস > প্রাইভেসি এবং সিকিউরিটিতে যান</string>
|
||||||
|
<string name="login_instructions_step3">৩. ইন্টিগ্রেশন > API টোকেনে ক্লিক করুন</string>
|
||||||
|
<string name="login_instructions_step4">৪. \"Masked Email\" স্কোপ সহ একটি নতুন টোকেন তৈরি করুন</string>
|
||||||
|
<string name="login_instructions_step5">৫. টোকেন কপি করুন এবং উপরে পেস্ট করুন</string>
|
||||||
|
<string name="login_instructions_full">১. Fastmail ওয়েব অ্যাপে লগইন করুন\n২. সেটিংস > প্রাইভেসি এবং সিকিউরিটিতে যান\n৩. ইন্টিগ্রেশন > API টোকেনে ক্লিক করুন\n৪. \"Masked Email\" স্কোপ সহ একটি নতুন টোকেন তৈরি করুন\n৫. টোকেন কপি করুন এবং উপরে পেস্ট করুন</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">মাস্কড ইমেইল</string>
|
||||||
|
<string name="email_list_search_placeholder">ইমেইল খুঁজুন…</string>
|
||||||
|
<string name="email_list_clear_search">সার্চ মুছুন</string>
|
||||||
|
<string name="email_list_filter_emails">ইমেইল ফিল্টার করুন</string>
|
||||||
|
<string name="email_list_create_fab">তৈরি করুন</string>
|
||||||
|
<string name="email_list_create_description">নতুন মাস্কড ইমেইল তৈরি করুন</string>
|
||||||
|
<string name="email_list_empty">কোনো মাস্কড ইমেইল পাওয়া যায়নি</string>
|
||||||
|
<string name="email_list_logout">লগআউট</string>
|
||||||
|
<string name="email_list_settings">সেটিংস</string>
|
||||||
|
<string name="filter_all">সব</string>
|
||||||
|
<string name="filter_enabled">সক্রিয়</string>
|
||||||
|
<string name="filter_disabled">নিষ্ক্রিয়</string>
|
||||||
|
<string name="filter_deleted">মুছে ফেলা</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">মাস্কড ইমেইল তৈরি করুন</string>
|
||||||
|
<string name="create_email_prefix_label">ইমেইল প্রিফিক্স (ঐচ্ছিক)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">যেমন, mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">সর্বোচ্চ ৬৪ অক্ষর: ছোট হাতের অক্ষর, সংখ্যা, আন্ডারস্কোর</string>
|
||||||
|
<string name="create_email_domain_label">সংশ্লিষ্ট ডোমেইন (ঐচ্ছিক)</string>
|
||||||
|
<string name="create_email_domain_placeholder">যেমন, example.com</string>
|
||||||
|
<string name="create_email_description_label">বিবরণ (ঐচ্ছিক)</string>
|
||||||
|
<string name="create_email_description_placeholder">যেমন, শপিং অ্যাকাউন্ট</string>
|
||||||
|
<string name="create_email_url_label">URL (ঐচ্ছিক)</string>
|
||||||
|
<string name="create_email_url_placeholder">যেমন, https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">প্রাথমিক অবস্থা</string>
|
||||||
|
<string name="create_email_button">মাস্কড ইমেইল তৈরি করুন</string>
|
||||||
|
<string name="create_email_created">তৈরি হয়েছে: %s</string>
|
||||||
|
<string name="create_email_copy_action">কপি</string>
|
||||||
|
<string name="navigate_back">পিছনে যান</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">ইমেইল বিবরণ</string>
|
||||||
|
<string name="email_detail_copy_email">ইমেইল ঠিকানা কপি করুন</string>
|
||||||
|
<string name="email_detail_delete">ইমেইল মুছুন</string>
|
||||||
|
<string name="email_detail_status">অবস্থা:</string>
|
||||||
|
<string name="email_detail_created_by">তৈরি করেছেন: %s</string>
|
||||||
|
<string name="email_detail_created">তৈরি: %s</string>
|
||||||
|
<string name="email_detail_last_message">শেষ বার্তা: %s</string>
|
||||||
|
<string name="email_detail_enable">সক্রিয় করুন</string>
|
||||||
|
<string name="email_detail_disable">নিষ্ক্রিয় করুন</string>
|
||||||
|
<string name="email_detail_edit_title">বিবরণ সম্পাদনা করুন</string>
|
||||||
|
<string name="email_detail_description_label">বিবরণ</string>
|
||||||
|
<string name="email_detail_domain_label">সংশ্লিষ্ট ডোমেইন</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">পরিবর্তন সংরক্ষণ করুন</string>
|
||||||
|
<string name="email_detail_updated">সফলভাবে আপডেট হয়েছে</string>
|
||||||
|
<string name="email_detail_deleted">সফলভাবে মুছে ফেলা হয়েছে</string>
|
||||||
|
<string name="email_detail_copied">ক্লিপবোর্ডে কপি হয়েছে</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">মাস্কড ইমেইল মুছুন</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">আপনি কি নিশ্চিত যে এই মাস্কড ইমেইলটি মুছতে চান? এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না।</string>
|
||||||
|
<string name="email_detail_delete_confirm">মুছুন</string>
|
||||||
|
<string name="email_detail_delete_cancel">বাতিল</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">সক্রিয়</string>
|
||||||
|
<string name="state_disabled">নিষ্ক্রিয়</string>
|
||||||
|
<string name="state_deleted">মুছে ফেলা</string>
|
||||||
|
<string name="state_pending">অপেক্ষমাণ</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">ইমেইল লোড করতে ব্যর্থ</string>
|
||||||
|
<string name="error_retry">পুনরায় চেষ্টা করুন</string>
|
||||||
|
<string name="error_generic">একটি ত্রুটি ঘটেছে</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">সেটিংস</string>
|
||||||
|
<string name="settings_language">ভাষা</string>
|
||||||
|
<string name="settings_language_description">অ্যাপের ভাষা নির্বাচন করুন</string>
|
||||||
|
<string name="settings_system_default">সিস্টেম ডিফল্ট</string>
|
||||||
|
<string name="settings_contact">যোগাযোগ এবং প্রতিক্রিয়া</string>
|
||||||
|
<string name="settings_contact_description">আমাদের আপনার প্রতিক্রিয়া পাঠান</string>
|
||||||
|
<string name="settings_logout">লগআউট</string>
|
||||||
|
<string name="settings_logout_description">আপনার অ্যাকাউন্ট থেকে সাইন আউট করুন</string>
|
||||||
|
<string name="settings_version">সংস্করণ %s</string>
|
||||||
|
<string name="settings_select_language">ভাষা নির্বাচন করুন</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask প্রতিক্রিয়া</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-de/strings.xml
Normal file
121
app/src/main/res/values-de/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail Maskierte-E-Mail-Manager</string>
|
||||||
|
<string name="login_api_token_label">API-Token</string>
|
||||||
|
<string name="login_api_token_placeholder">Geben Sie Ihr Fastmail API-Token ein</string>
|
||||||
|
<string name="login_button">Anmelden</string>
|
||||||
|
<string name="login_show_token">Token anzeigen</string>
|
||||||
|
<string name="login_hide_token">Token ausblenden</string>
|
||||||
|
<string name="login_instructions_title">So erhalten Sie Ihr API-Token:</string>
|
||||||
|
<string name="login_instructions_step1">1. Melden Sie sich bei der Fastmail Web-App an</string>
|
||||||
|
<string name="login_instructions_step2">2. Gehen Sie zu Einstellungen > Datenschutz und Sicherheit</string>
|
||||||
|
<string name="login_instructions_step3">3. Klicken Sie auf Integrationen > API-Token</string>
|
||||||
|
<string name="login_instructions_step4">4. Erstellen Sie ein neues Token mit dem Bereich \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Kopieren Sie das Token und fügen Sie es oben ein</string>
|
||||||
|
<string name="login_instructions_full">1. Melden Sie sich bei der Fastmail Web-App an\n2. Gehen Sie zu Einstellungen > Datenschutz und Sicherheit\n3. Klicken Sie auf Integrationen > API-Token\n4. Erstellen Sie ein neues Token mit dem Bereich \"Masked Email\"\n5. Kopieren Sie das Token und fügen Sie es oben ein</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Maskierte E-Mails</string>
|
||||||
|
<string name="email_list_search_placeholder">E-Mails suchen…</string>
|
||||||
|
<string name="email_list_clear_search">Suche löschen</string>
|
||||||
|
<string name="email_list_filter_emails">E-Mails filtern</string>
|
||||||
|
<string name="email_list_create_fab">Erstellen</string>
|
||||||
|
<string name="email_list_create_description">Neue maskierte E-Mail erstellen</string>
|
||||||
|
<string name="email_list_empty">Keine maskierten E-Mails gefunden</string>
|
||||||
|
<string name="email_list_logout">Abmelden</string>
|
||||||
|
<string name="email_list_settings">Einstellungen</string>
|
||||||
|
<string name="filter_all">Alle</string>
|
||||||
|
<string name="filter_enabled">Aktiviert</string>
|
||||||
|
<string name="filter_disabled">Deaktiviert</string>
|
||||||
|
<string name="filter_deleted">Gelöscht</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Maskierte E-Mail erstellen</string>
|
||||||
|
<string name="create_email_prefix_label">E-Mail-Präfix (optional)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">z.B. meinseite_einkauf</string>
|
||||||
|
<string name="create_email_prefix_hint">Max. 64 Zeichen: Kleinbuchstaben, Zahlen, Unterstriche</string>
|
||||||
|
<string name="create_email_domain_label">Zugehörige Domain (optional)</string>
|
||||||
|
<string name="create_email_domain_placeholder">z.B. beispiel.de</string>
|
||||||
|
<string name="create_email_description_label">Beschreibung (optional)</string>
|
||||||
|
<string name="create_email_description_placeholder">z.B. Einkaufskonto</string>
|
||||||
|
<string name="create_email_url_label">URL (optional)</string>
|
||||||
|
<string name="create_email_url_placeholder">z.B. https://beispiel.de</string>
|
||||||
|
<string name="create_email_initial_state">Anfangszustand</string>
|
||||||
|
<string name="create_email_button">Maskierte E-Mail erstellen</string>
|
||||||
|
<string name="create_email_created">Erstellt: %s</string>
|
||||||
|
<string name="create_email_copy_action">Kopieren</string>
|
||||||
|
<string name="navigate_back">Zurück</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">E-Mail-Details</string>
|
||||||
|
<string name="email_detail_copy_email">E-Mail-Adresse kopieren</string>
|
||||||
|
<string name="email_detail_delete">E-Mail löschen</string>
|
||||||
|
<string name="email_detail_status">Status:</string>
|
||||||
|
<string name="email_detail_created_by">Erstellt von: %s</string>
|
||||||
|
<string name="email_detail_created">Erstellt: %s</string>
|
||||||
|
<string name="email_detail_last_message">Letzte Nachricht: %s</string>
|
||||||
|
<string name="email_detail_enable">Aktivieren</string>
|
||||||
|
<string name="email_detail_disable">Deaktivieren</string>
|
||||||
|
<string name="email_detail_edit_title">Details bearbeiten</string>
|
||||||
|
<string name="email_detail_description_label">Beschreibung</string>
|
||||||
|
<string name="email_detail_domain_label">Zugehörige Domain</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Änderungen speichern</string>
|
||||||
|
<string name="email_detail_updated">Erfolgreich aktualisiert</string>
|
||||||
|
<string name="email_detail_deleted">Erfolgreich gelöscht</string>
|
||||||
|
<string name="email_detail_copied">In Zwischenablage kopiert</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Maskierte E-Mail löschen</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Sind Sie sicher, dass Sie diese maskierte E-Mail löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Löschen</string>
|
||||||
|
<string name="email_detail_delete_cancel">Abbrechen</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Aktiviert</string>
|
||||||
|
<string name="state_disabled">Deaktiviert</string>
|
||||||
|
<string name="state_deleted">Gelöscht</string>
|
||||||
|
<string name="state_pending">Ausstehend</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">E-Mails konnten nicht geladen werden</string>
|
||||||
|
<string name="error_retry">Erneut versuchen</string>
|
||||||
|
<string name="error_generic">Ein Fehler ist aufgetreten</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Einstellungen</string>
|
||||||
|
<string name="settings_language">Sprache</string>
|
||||||
|
<string name="settings_language_description">App-Sprache auswählen</string>
|
||||||
|
<string name="settings_system_default">Systemstandard</string>
|
||||||
|
<string name="settings_contact">Kontakt und Feedback</string>
|
||||||
|
<string name="settings_contact_description">Senden Sie uns Ihr Feedback</string>
|
||||||
|
<string name="settings_logout">Abmelden</string>
|
||||||
|
<string name="settings_logout_description">Von Ihrem Konto abmelden</string>
|
||||||
|
<string name="settings_version">Version %s</string>
|
||||||
|
<string name="settings_select_language">Sprache auswählen</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask Feedback</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-es/strings.xml
Normal file
121
app/src/main/res/values-es/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Gestor de correos enmascarados de Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Token de API</string>
|
||||||
|
<string name="login_api_token_placeholder">Introduce tu token de API de Fastmail</string>
|
||||||
|
<string name="login_button">Iniciar sesión</string>
|
||||||
|
<string name="login_show_token">Mostrar token</string>
|
||||||
|
<string name="login_hide_token">Ocultar token</string>
|
||||||
|
<string name="login_instructions_title">Cómo obtener tu token de API:</string>
|
||||||
|
<string name="login_instructions_step1">1. Inicia sesión en la aplicación web de Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Ve a Configuración > Privacidad y Seguridad</string>
|
||||||
|
<string name="login_instructions_step3">3. Haz clic en Integraciones > Tokens de API</string>
|
||||||
|
<string name="login_instructions_step4">4. Crea un nuevo token con el alcance \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Copia el token y pégalo arriba</string>
|
||||||
|
<string name="login_instructions_full">1. Inicia sesión en la aplicación web de Fastmail\n2. Ve a Configuración > Privacidad y Seguridad\n3. Haz clic en Integraciones > Tokens de API\n4. Crea un nuevo token con el alcance \"Masked Email\"\n5. Copia el token y pégalo arriba</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Correos enmascarados</string>
|
||||||
|
<string name="email_list_search_placeholder">Buscar correos…</string>
|
||||||
|
<string name="email_list_clear_search">Borrar búsqueda</string>
|
||||||
|
<string name="email_list_filter_emails">Filtrar correos</string>
|
||||||
|
<string name="email_list_create_fab">Crear</string>
|
||||||
|
<string name="email_list_create_description">Crear nuevo correo enmascarado</string>
|
||||||
|
<string name="email_list_empty">No se encontraron correos enmascarados</string>
|
||||||
|
<string name="email_list_logout">Cerrar sesión</string>
|
||||||
|
<string name="email_list_settings">Configuración</string>
|
||||||
|
<string name="filter_all">Todos</string>
|
||||||
|
<string name="filter_enabled">Habilitados</string>
|
||||||
|
<string name="filter_disabled">Deshabilitados</string>
|
||||||
|
<string name="filter_deleted">Eliminados</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Crear correo enmascarado</string>
|
||||||
|
<string name="create_email_prefix_label">Prefijo de correo (opcional)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">ej., misitio_compras</string>
|
||||||
|
<string name="create_email_prefix_hint">Máx 64 caracteres: letras minúsculas, números, guiones bajos</string>
|
||||||
|
<string name="create_email_domain_label">Dominio asociado (opcional)</string>
|
||||||
|
<string name="create_email_domain_placeholder">ej., ejemplo.com</string>
|
||||||
|
<string name="create_email_description_label">Descripción (opcional)</string>
|
||||||
|
<string name="create_email_description_placeholder">ej., Cuenta de compras</string>
|
||||||
|
<string name="create_email_url_label">URL (opcional)</string>
|
||||||
|
<string name="create_email_url_placeholder">ej., https://ejemplo.com</string>
|
||||||
|
<string name="create_email_initial_state">Estado inicial</string>
|
||||||
|
<string name="create_email_button">Crear correo enmascarado</string>
|
||||||
|
<string name="create_email_created">Creado: %s</string>
|
||||||
|
<string name="create_email_copy_action">Copiar</string>
|
||||||
|
<string name="navigate_back">Volver</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Detalles del correo</string>
|
||||||
|
<string name="email_detail_copy_email">Copiar dirección de correo</string>
|
||||||
|
<string name="email_detail_delete">Eliminar correo</string>
|
||||||
|
<string name="email_detail_status">Estado:</string>
|
||||||
|
<string name="email_detail_created_by">Creado por: %s</string>
|
||||||
|
<string name="email_detail_created">Creado: %s</string>
|
||||||
|
<string name="email_detail_last_message">Último mensaje: %s</string>
|
||||||
|
<string name="email_detail_enable">Habilitar</string>
|
||||||
|
<string name="email_detail_disable">Deshabilitar</string>
|
||||||
|
<string name="email_detail_edit_title">Editar detalles</string>
|
||||||
|
<string name="email_detail_description_label">Descripción</string>
|
||||||
|
<string name="email_detail_domain_label">Dominio asociado</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Guardar cambios</string>
|
||||||
|
<string name="email_detail_updated">Actualizado correctamente</string>
|
||||||
|
<string name="email_detail_deleted">Eliminado correctamente</string>
|
||||||
|
<string name="email_detail_copied">Copiado al portapapeles</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Eliminar correo enmascarado</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">¿Estás seguro de que quieres eliminar este correo enmascarado? Esta acción no se puede deshacer.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Eliminar</string>
|
||||||
|
<string name="email_detail_delete_cancel">Cancelar</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Habilitado</string>
|
||||||
|
<string name="state_disabled">Deshabilitado</string>
|
||||||
|
<string name="state_deleted">Eliminado</string>
|
||||||
|
<string name="state_pending">Pendiente</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Error al cargar los correos</string>
|
||||||
|
<string name="error_retry">Reintentar</string>
|
||||||
|
<string name="error_generic">Se produjo un error</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Configuración</string>
|
||||||
|
<string name="settings_language">Idioma</string>
|
||||||
|
<string name="settings_language_description">Seleccionar idioma de la app</string>
|
||||||
|
<string name="settings_system_default">Predeterminado del sistema</string>
|
||||||
|
<string name="settings_contact">Contacto y comentarios</string>
|
||||||
|
<string name="settings_contact_description">Envíanos tus comentarios</string>
|
||||||
|
<string name="settings_logout">Cerrar sesión</string>
|
||||||
|
<string name="settings_logout_description">Salir de tu cuenta</string>
|
||||||
|
<string name="settings_version">Versión %s</string>
|
||||||
|
<string name="settings_select_language">Seleccionar idioma</string>
|
||||||
|
<string name="settings_feedback_subject">Comentarios de FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-fr/strings.xml
Normal file
121
app/src/main/res/values-fr/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Gestionnaire d\'emails masqués Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Jeton API</string>
|
||||||
|
<string name="login_api_token_placeholder">Entrez votre jeton API Fastmail</string>
|
||||||
|
<string name="login_button">Connexion</string>
|
||||||
|
<string name="login_show_token">Afficher le jeton</string>
|
||||||
|
<string name="login_hide_token">Masquer le jeton</string>
|
||||||
|
<string name="login_instructions_title">Comment obtenir votre jeton API :</string>
|
||||||
|
<string name="login_instructions_step1">1. Connectez-vous à l\'application web Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Allez dans Paramètres > Confidentialité et sécurité</string>
|
||||||
|
<string name="login_instructions_step3">3. Cliquez sur Intégrations > Jetons API</string>
|
||||||
|
<string name="login_instructions_step4">4. Créez un nouveau jeton avec la portée \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Copiez le jeton et collez-le ci-dessus</string>
|
||||||
|
<string name="login_instructions_full">1. Connectez-vous à l\'application web Fastmail\n2. Allez dans Paramètres > Confidentialité et sécurité\n3. Cliquez sur Intégrations > Jetons API\n4. Créez un nouveau jeton avec la portée \"Masked Email\"\n5. Copiez le jeton et collez-le ci-dessus</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Emails masqués</string>
|
||||||
|
<string name="email_list_search_placeholder">Rechercher des emails…</string>
|
||||||
|
<string name="email_list_clear_search">Effacer la recherche</string>
|
||||||
|
<string name="email_list_filter_emails">Filtrer les emails</string>
|
||||||
|
<string name="email_list_create_fab">Créer</string>
|
||||||
|
<string name="email_list_create_description">Créer un nouvel email masqué</string>
|
||||||
|
<string name="email_list_empty">Aucun email masqué trouvé</string>
|
||||||
|
<string name="email_list_logout">Déconnexion</string>
|
||||||
|
<string name="email_list_settings">Paramètres</string>
|
||||||
|
<string name="filter_all">Tous</string>
|
||||||
|
<string name="filter_enabled">Activés</string>
|
||||||
|
<string name="filter_disabled">Désactivés</string>
|
||||||
|
<string name="filter_deleted">Supprimés</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Créer un email masqué</string>
|
||||||
|
<string name="create_email_prefix_label">Préfixe d\'email (optionnel)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">ex. : monsite_achats</string>
|
||||||
|
<string name="create_email_prefix_hint">Max 64 caractères : lettres minuscules, chiffres, tirets bas</string>
|
||||||
|
<string name="create_email_domain_label">Domaine associé (optionnel)</string>
|
||||||
|
<string name="create_email_domain_placeholder">ex. : exemple.com</string>
|
||||||
|
<string name="create_email_description_label">Description (optionnel)</string>
|
||||||
|
<string name="create_email_description_placeholder">ex. : Compte d\'achats</string>
|
||||||
|
<string name="create_email_url_label">URL (optionnel)</string>
|
||||||
|
<string name="create_email_url_placeholder">ex. : https://exemple.com</string>
|
||||||
|
<string name="create_email_initial_state">État initial</string>
|
||||||
|
<string name="create_email_button">Créer l\'email masqué</string>
|
||||||
|
<string name="create_email_created">Créé : %s</string>
|
||||||
|
<string name="create_email_copy_action">Copier</string>
|
||||||
|
<string name="navigate_back">Retour</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Détails de l\'email</string>
|
||||||
|
<string name="email_detail_copy_email">Copier l\'adresse email</string>
|
||||||
|
<string name="email_detail_delete">Supprimer l\'email</string>
|
||||||
|
<string name="email_detail_status">Statut :</string>
|
||||||
|
<string name="email_detail_created_by">Créé par : %s</string>
|
||||||
|
<string name="email_detail_created">Créé : %s</string>
|
||||||
|
<string name="email_detail_last_message">Dernier message : %s</string>
|
||||||
|
<string name="email_detail_enable">Activer</string>
|
||||||
|
<string name="email_detail_disable">Désactiver</string>
|
||||||
|
<string name="email_detail_edit_title">Modifier les détails</string>
|
||||||
|
<string name="email_detail_description_label">Description</string>
|
||||||
|
<string name="email_detail_domain_label">Domaine associé</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Enregistrer les modifications</string>
|
||||||
|
<string name="email_detail_updated">Mis à jour avec succès</string>
|
||||||
|
<string name="email_detail_deleted">Supprimé avec succès</string>
|
||||||
|
<string name="email_detail_copied">Copié dans le presse-papiers</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Supprimer l\'email masqué</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Êtes-vous sûr de vouloir supprimer cet email masqué ? Cette action est irréversible.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Supprimer</string>
|
||||||
|
<string name="email_detail_delete_cancel">Annuler</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Activé</string>
|
||||||
|
<string name="state_disabled">Désactivé</string>
|
||||||
|
<string name="state_deleted">Supprimé</string>
|
||||||
|
<string name="state_pending">En attente</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Échec du chargement des emails</string>
|
||||||
|
<string name="error_retry">Réessayer</string>
|
||||||
|
<string name="error_generic">Une erreur s\'est produite</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Paramètres</string>
|
||||||
|
<string name="settings_language">Langue</string>
|
||||||
|
<string name="settings_language_description">Sélectionner la langue de l\'app</string>
|
||||||
|
<string name="settings_system_default">Par défaut du système</string>
|
||||||
|
<string name="settings_contact">Contact et commentaires</string>
|
||||||
|
<string name="settings_contact_description">Envoyez-nous vos commentaires</string>
|
||||||
|
<string name="settings_logout">Déconnexion</string>
|
||||||
|
<string name="settings_logout_description">Se déconnecter de votre compte</string>
|
||||||
|
<string name="settings_version">Version %s</string>
|
||||||
|
<string name="settings_select_language">Sélectionner la langue</string>
|
||||||
|
<string name="settings_feedback_subject">Commentaires FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-hi/strings.xml
Normal file
121
app/src/main/res/values-hi/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail मास्क्ड ईमेल प्रबंधक</string>
|
||||||
|
<string name="login_api_token_label">API टोकन</string>
|
||||||
|
<string name="login_api_token_placeholder">अपना Fastmail API टोकन दर्ज करें</string>
|
||||||
|
<string name="login_button">लॉग इन करें</string>
|
||||||
|
<string name="login_show_token">टोकन दिखाएं</string>
|
||||||
|
<string name="login_hide_token">टोकन छुपाएं</string>
|
||||||
|
<string name="login_instructions_title">अपना API टोकन कैसे प्राप्त करें:</string>
|
||||||
|
<string name="login_instructions_step1">1. Fastmail वेब ऐप में लॉग इन करें</string>
|
||||||
|
<string name="login_instructions_step2">2. सेटिंग्स > गोपनीयता और सुरक्षा पर जाएं</string>
|
||||||
|
<string name="login_instructions_step3">3. इंटीग्रेशन > API टोकन पर क्लिक करें</string>
|
||||||
|
<string name="login_instructions_step4">4. \"Masked Email\" स्कोप के साथ एक नया टोकन बनाएं</string>
|
||||||
|
<string name="login_instructions_step5">5. टोकन कॉपी करें और ऊपर पेस्ट करें</string>
|
||||||
|
<string name="login_instructions_full">1. Fastmail वेब ऐप में लॉग इन करें\n2. सेटिंग्स > गोपनीयता और सुरक्षा पर जाएं\n3. इंटीग्रेशन > API टोकन पर क्लिक करें\n4. \"Masked Email\" स्कोप के साथ एक नया टोकन बनाएं\n5. टोकन कॉपी करें और ऊपर पेस्ट करें</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">मास्क्ड ईमेल</string>
|
||||||
|
<string name="email_list_search_placeholder">ईमेल खोजें…</string>
|
||||||
|
<string name="email_list_clear_search">खोज साफ़ करें</string>
|
||||||
|
<string name="email_list_filter_emails">ईमेल फ़िल्टर करें</string>
|
||||||
|
<string name="email_list_create_fab">बनाएं</string>
|
||||||
|
<string name="email_list_create_description">नया मास्क्ड ईमेल बनाएं</string>
|
||||||
|
<string name="email_list_empty">कोई मास्क्ड ईमेल नहीं मिला</string>
|
||||||
|
<string name="email_list_logout">लॉग आउट</string>
|
||||||
|
<string name="email_list_settings">सेटिंग्स</string>
|
||||||
|
<string name="filter_all">सभी</string>
|
||||||
|
<string name="filter_enabled">सक्षम</string>
|
||||||
|
<string name="filter_disabled">अक्षम</string>
|
||||||
|
<string name="filter_deleted">हटाए गए</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">मास्क्ड ईमेल बनाएं</string>
|
||||||
|
<string name="create_email_prefix_label">ईमेल प्रीफ़िक्स (वैकल्पिक)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">जैसे, mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">अधिकतम 64 अक्षर: छोटे अक्षर, संख्याएं, अंडरस्कोर</string>
|
||||||
|
<string name="create_email_domain_label">संबंधित डोमेन (वैकल्पिक)</string>
|
||||||
|
<string name="create_email_domain_placeholder">जैसे, example.com</string>
|
||||||
|
<string name="create_email_description_label">विवरण (वैकल्पिक)</string>
|
||||||
|
<string name="create_email_description_placeholder">जैसे, शॉपिंग अकाउंट</string>
|
||||||
|
<string name="create_email_url_label">URL (वैकल्पिक)</string>
|
||||||
|
<string name="create_email_url_placeholder">जैसे, https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">प्रारंभिक स्थिति</string>
|
||||||
|
<string name="create_email_button">मास्क्ड ईमेल बनाएं</string>
|
||||||
|
<string name="create_email_created">बनाया गया: %s</string>
|
||||||
|
<string name="create_email_copy_action">कॉपी करें</string>
|
||||||
|
<string name="navigate_back">वापस जाएं</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">ईमेल विवरण</string>
|
||||||
|
<string name="email_detail_copy_email">ईमेल पता कॉपी करें</string>
|
||||||
|
<string name="email_detail_delete">ईमेल हटाएं</string>
|
||||||
|
<string name="email_detail_status">स्थिति:</string>
|
||||||
|
<string name="email_detail_created_by">बनाने वाला: %s</string>
|
||||||
|
<string name="email_detail_created">बनाया गया: %s</string>
|
||||||
|
<string name="email_detail_last_message">अंतिम संदेश: %s</string>
|
||||||
|
<string name="email_detail_enable">सक्षम करें</string>
|
||||||
|
<string name="email_detail_disable">अक्षम करें</string>
|
||||||
|
<string name="email_detail_edit_title">विवरण संपादित करें</string>
|
||||||
|
<string name="email_detail_description_label">विवरण</string>
|
||||||
|
<string name="email_detail_domain_label">संबंधित डोमेन</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">परिवर्तन सहेजें</string>
|
||||||
|
<string name="email_detail_updated">सफलतापूर्वक अपडेट किया गया</string>
|
||||||
|
<string name="email_detail_deleted">सफलतापूर्वक हटाया गया</string>
|
||||||
|
<string name="email_detail_copied">क्लिपबोर्ड पर कॉपी किया गया</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">मास्क्ड ईमेल हटाएं</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">क्या आप वाकई इस मास्क्ड ईमेल को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।</string>
|
||||||
|
<string name="email_detail_delete_confirm">हटाएं</string>
|
||||||
|
<string name="email_detail_delete_cancel">रद्द करें</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">सक्षम</string>
|
||||||
|
<string name="state_disabled">अक्षम</string>
|
||||||
|
<string name="state_deleted">हटाया गया</string>
|
||||||
|
<string name="state_pending">लंबित</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">ईमेल लोड करने में विफल</string>
|
||||||
|
<string name="error_retry">पुनः प्रयास करें</string>
|
||||||
|
<string name="error_generic">एक त्रुटि हुई</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">सेटिंग्स</string>
|
||||||
|
<string name="settings_language">भाषा</string>
|
||||||
|
<string name="settings_language_description">ऐप की भाषा चुनें</string>
|
||||||
|
<string name="settings_system_default">सिस्टम डिफ़ॉल्ट</string>
|
||||||
|
<string name="settings_contact">संपर्क और फ़ीडबैक</string>
|
||||||
|
<string name="settings_contact_description">हमें अपना फ़ीडबैक भेजें</string>
|
||||||
|
<string name="settings_logout">लॉग आउट</string>
|
||||||
|
<string name="settings_logout_description">अपने खाते से साइन आउट करें</string>
|
||||||
|
<string name="settings_version">संस्करण %s</string>
|
||||||
|
<string name="settings_select_language">भाषा चुनें</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask फ़ीडबैक</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-id/strings.xml
Normal file
121
app/src/main/res/values-id/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Pengelola Email Tersamar Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Token API</string>
|
||||||
|
<string name="login_api_token_placeholder">Masukkan token API Fastmail Anda</string>
|
||||||
|
<string name="login_button">Masuk</string>
|
||||||
|
<string name="login_show_token">Tampilkan token</string>
|
||||||
|
<string name="login_hide_token">Sembunyikan token</string>
|
||||||
|
<string name="login_instructions_title">Cara mendapatkan token API:</string>
|
||||||
|
<string name="login_instructions_step1">1. Masuk ke aplikasi web Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Buka Pengaturan > Privasi & Keamanan</string>
|
||||||
|
<string name="login_instructions_step3">3. Klik Integrasi > Token API</string>
|
||||||
|
<string name="login_instructions_step4">4. Buat token baru dengan cakupan \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Salin token dan tempel di atas</string>
|
||||||
|
<string name="login_instructions_full">1. Masuk ke aplikasi web Fastmail\n2. Buka Pengaturan > Privasi & Keamanan\n3. Klik Integrasi > Token API\n4. Buat token baru dengan cakupan \"Masked Email\"\n5. Salin token dan tempel di atas</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Email Tersamar</string>
|
||||||
|
<string name="email_list_search_placeholder">Cari email…</string>
|
||||||
|
<string name="email_list_clear_search">Hapus pencarian</string>
|
||||||
|
<string name="email_list_filter_emails">Filter email</string>
|
||||||
|
<string name="email_list_create_fab">Buat</string>
|
||||||
|
<string name="email_list_create_description">Buat email tersamar baru</string>
|
||||||
|
<string name="email_list_empty">Tidak ada email tersamar ditemukan</string>
|
||||||
|
<string name="email_list_logout">Keluar</string>
|
||||||
|
<string name="email_list_settings">Pengaturan</string>
|
||||||
|
<string name="filter_all">Semua</string>
|
||||||
|
<string name="filter_enabled">Aktif</string>
|
||||||
|
<string name="filter_disabled">Nonaktif</string>
|
||||||
|
<string name="filter_deleted">Dihapus</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Buat Email Tersamar</string>
|
||||||
|
<string name="create_email_prefix_label">Awalan email (opsional)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">mis., situsku_belanja</string>
|
||||||
|
<string name="create_email_prefix_hint">Maks 64 karakter: huruf kecil, angka, garis bawah</string>
|
||||||
|
<string name="create_email_domain_label">Domain terkait (opsional)</string>
|
||||||
|
<string name="create_email_domain_placeholder">mis., contoh.com</string>
|
||||||
|
<string name="create_email_description_label">Deskripsi (opsional)</string>
|
||||||
|
<string name="create_email_description_placeholder">mis., Akun belanja</string>
|
||||||
|
<string name="create_email_url_label">URL (opsional)</string>
|
||||||
|
<string name="create_email_url_placeholder">mis., https://contoh.com</string>
|
||||||
|
<string name="create_email_initial_state">Status awal</string>
|
||||||
|
<string name="create_email_button">Buat Email Tersamar</string>
|
||||||
|
<string name="create_email_created">Dibuat: %s</string>
|
||||||
|
<string name="create_email_copy_action">Salin</string>
|
||||||
|
<string name="navigate_back">Kembali</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Detail Email</string>
|
||||||
|
<string name="email_detail_copy_email">Salin alamat email</string>
|
||||||
|
<string name="email_detail_delete">Hapus email</string>
|
||||||
|
<string name="email_detail_status">Status:</string>
|
||||||
|
<string name="email_detail_created_by">Dibuat oleh: %s</string>
|
||||||
|
<string name="email_detail_created">Dibuat: %s</string>
|
||||||
|
<string name="email_detail_last_message">Pesan terakhir: %s</string>
|
||||||
|
<string name="email_detail_enable">Aktifkan</string>
|
||||||
|
<string name="email_detail_disable">Nonaktifkan</string>
|
||||||
|
<string name="email_detail_edit_title">Edit Detail</string>
|
||||||
|
<string name="email_detail_description_label">Deskripsi</string>
|
||||||
|
<string name="email_detail_domain_label">Domain terkait</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Simpan Perubahan</string>
|
||||||
|
<string name="email_detail_updated">Berhasil diperbarui</string>
|
||||||
|
<string name="email_detail_deleted">Berhasil dihapus</string>
|
||||||
|
<string name="email_detail_copied">Disalin ke papan klip</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Hapus Email Tersamar</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Apakah Anda yakin ingin menghapus email tersamar ini? Tindakan ini tidak dapat dibatalkan.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Hapus</string>
|
||||||
|
<string name="email_detail_delete_cancel">Batal</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Aktif</string>
|
||||||
|
<string name="state_disabled">Nonaktif</string>
|
||||||
|
<string name="state_deleted">Dihapus</string>
|
||||||
|
<string name="state_pending">Tertunda</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Gagal memuat email</string>
|
||||||
|
<string name="error_retry">Coba Lagi</string>
|
||||||
|
<string name="error_generic">Terjadi kesalahan</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Pengaturan</string>
|
||||||
|
<string name="settings_language">Bahasa</string>
|
||||||
|
<string name="settings_language_description">Pilih bahasa aplikasi</string>
|
||||||
|
<string name="settings_system_default">Default Sistem</string>
|
||||||
|
<string name="settings_contact">Kontak & Masukan</string>
|
||||||
|
<string name="settings_contact_description">Kirimkan masukan Anda</string>
|
||||||
|
<string name="settings_logout">Keluar</string>
|
||||||
|
<string name="settings_logout_description">Keluar dari akun Anda</string>
|
||||||
|
<string name="settings_version">Versi %s</string>
|
||||||
|
<string name="settings_select_language">Pilih Bahasa</string>
|
||||||
|
<string name="settings_feedback_subject">Masukan FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-it/strings.xml
Normal file
121
app/src/main/res/values-it/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Gestore email mascherate di Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Token API</string>
|
||||||
|
<string name="login_api_token_placeholder">Inserisci il tuo token API Fastmail</string>
|
||||||
|
<string name="login_button">Accedi</string>
|
||||||
|
<string name="login_show_token">Mostra token</string>
|
||||||
|
<string name="login_hide_token">Nascondi token</string>
|
||||||
|
<string name="login_instructions_title">Come ottenere il tuo token API:</string>
|
||||||
|
<string name="login_instructions_step1">1. Accedi all\'app web di Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Vai su Impostazioni > Privacy e Sicurezza</string>
|
||||||
|
<string name="login_instructions_step3">3. Clicca su Integrazioni > Token API</string>
|
||||||
|
<string name="login_instructions_step4">4. Crea un nuovo token con ambito \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Copia il token e incollalo sopra</string>
|
||||||
|
<string name="login_instructions_full">1. Accedi all\'app web di Fastmail\n2. Vai su Impostazioni > Privacy e Sicurezza\n3. Clicca su Integrazioni > Token API\n4. Crea un nuovo token con ambito \"Masked Email\"\n5. Copia il token e incollalo sopra</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Email mascherate</string>
|
||||||
|
<string name="email_list_search_placeholder">Cerca email…</string>
|
||||||
|
<string name="email_list_clear_search">Cancella ricerca</string>
|
||||||
|
<string name="email_list_filter_emails">Filtra email</string>
|
||||||
|
<string name="email_list_create_fab">Crea</string>
|
||||||
|
<string name="email_list_create_description">Crea nuova email mascherata</string>
|
||||||
|
<string name="email_list_empty">Nessuna email mascherata trovata</string>
|
||||||
|
<string name="email_list_logout">Esci</string>
|
||||||
|
<string name="email_list_settings">Impostazioni</string>
|
||||||
|
<string name="filter_all">Tutte</string>
|
||||||
|
<string name="filter_enabled">Attive</string>
|
||||||
|
<string name="filter_disabled">Disattivate</string>
|
||||||
|
<string name="filter_deleted">Eliminate</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Crea email mascherata</string>
|
||||||
|
<string name="create_email_prefix_label">Prefisso email (opzionale)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">es. miosito_acquisti</string>
|
||||||
|
<string name="create_email_prefix_hint">Max 64 caratteri: lettere minuscole, numeri, trattini bassi</string>
|
||||||
|
<string name="create_email_domain_label">Dominio associato (opzionale)</string>
|
||||||
|
<string name="create_email_domain_placeholder">es. esempio.it</string>
|
||||||
|
<string name="create_email_description_label">Descrizione (opzionale)</string>
|
||||||
|
<string name="create_email_description_placeholder">es. Account acquisti</string>
|
||||||
|
<string name="create_email_url_label">URL (opzionale)</string>
|
||||||
|
<string name="create_email_url_placeholder">es. https://esempio.it</string>
|
||||||
|
<string name="create_email_initial_state">Stato iniziale</string>
|
||||||
|
<string name="create_email_button">Crea email mascherata</string>
|
||||||
|
<string name="create_email_created">Creata: %s</string>
|
||||||
|
<string name="create_email_copy_action">Copia</string>
|
||||||
|
<string name="navigate_back">Indietro</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Dettagli email</string>
|
||||||
|
<string name="email_detail_copy_email">Copia indirizzo email</string>
|
||||||
|
<string name="email_detail_delete">Elimina email</string>
|
||||||
|
<string name="email_detail_status">Stato:</string>
|
||||||
|
<string name="email_detail_created_by">Creata da: %s</string>
|
||||||
|
<string name="email_detail_created">Creata: %s</string>
|
||||||
|
<string name="email_detail_last_message">Ultimo messaggio: %s</string>
|
||||||
|
<string name="email_detail_enable">Attiva</string>
|
||||||
|
<string name="email_detail_disable">Disattiva</string>
|
||||||
|
<string name="email_detail_edit_title">Modifica dettagli</string>
|
||||||
|
<string name="email_detail_description_label">Descrizione</string>
|
||||||
|
<string name="email_detail_domain_label">Dominio associato</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Salva modifiche</string>
|
||||||
|
<string name="email_detail_updated">Aggiornato con successo</string>
|
||||||
|
<string name="email_detail_deleted">Eliminato con successo</string>
|
||||||
|
<string name="email_detail_copied">Copiato negli appunti</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Elimina email mascherata</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Sei sicuro di voler eliminare questa email mascherata? Questa azione non può essere annullata.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Elimina</string>
|
||||||
|
<string name="email_detail_delete_cancel">Annulla</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Attiva</string>
|
||||||
|
<string name="state_disabled">Disattivata</string>
|
||||||
|
<string name="state_deleted">Eliminata</string>
|
||||||
|
<string name="state_pending">In attesa</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Impossibile caricare le email</string>
|
||||||
|
<string name="error_retry">Riprova</string>
|
||||||
|
<string name="error_generic">Si è verificato un errore</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Impostazioni</string>
|
||||||
|
<string name="settings_language">Lingua</string>
|
||||||
|
<string name="settings_language_description">Seleziona la lingua dell\'app</string>
|
||||||
|
<string name="settings_system_default">Predefinito di sistema</string>
|
||||||
|
<string name="settings_contact">Contatto e feedback</string>
|
||||||
|
<string name="settings_contact_description">Inviaci il tuo feedback</string>
|
||||||
|
<string name="settings_logout">Esci</string>
|
||||||
|
<string name="settings_logout_description">Disconnettiti dal tuo account</string>
|
||||||
|
<string name="settings_version">Versione %s</string>
|
||||||
|
<string name="settings_select_language">Seleziona lingua</string>
|
||||||
|
<string name="settings_feedback_subject">Feedback FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-ja/strings.xml
Normal file
121
app/src/main/res/values-ja/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail マスクメール管理</string>
|
||||||
|
<string name="login_api_token_label">APIトークン</string>
|
||||||
|
<string name="login_api_token_placeholder">Fastmail APIトークンを入力</string>
|
||||||
|
<string name="login_button">ログイン</string>
|
||||||
|
<string name="login_show_token">トークンを表示</string>
|
||||||
|
<string name="login_hide_token">トークンを非表示</string>
|
||||||
|
<string name="login_instructions_title">APIトークンの取得方法:</string>
|
||||||
|
<string name="login_instructions_step1">1. Fastmail Webアプリにログイン</string>
|
||||||
|
<string name="login_instructions_step2">2. 設定 > プライバシーとセキュリティに移動</string>
|
||||||
|
<string name="login_instructions_step3">3. 連携 > APIトークンをクリック</string>
|
||||||
|
<string name="login_instructions_step4">4. 「Masked Email」スコープで新しいトークンを作成</string>
|
||||||
|
<string name="login_instructions_step5">5. トークンをコピーして上に貼り付け</string>
|
||||||
|
<string name="login_instructions_full">1. Fastmail Webアプリにログイン\n2. 設定 > プライバシーとセキュリティに移動\n3. 連携 > APIトークンをクリック\n4. 「Masked Email」スコープで新しいトークンを作成\n5. トークンをコピーして上に貼り付け</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">マスクメール</string>
|
||||||
|
<string name="email_list_search_placeholder">メールを検索…</string>
|
||||||
|
<string name="email_list_clear_search">検索をクリア</string>
|
||||||
|
<string name="email_list_filter_emails">メールをフィルター</string>
|
||||||
|
<string name="email_list_create_fab">作成</string>
|
||||||
|
<string name="email_list_create_description">新しいマスクメールを作成</string>
|
||||||
|
<string name="email_list_empty">マスクメールが見つかりません</string>
|
||||||
|
<string name="email_list_logout">ログアウト</string>
|
||||||
|
<string name="email_list_settings">設定</string>
|
||||||
|
<string name="filter_all">すべて</string>
|
||||||
|
<string name="filter_enabled">有効</string>
|
||||||
|
<string name="filter_disabled">無効</string>
|
||||||
|
<string name="filter_deleted">削除済み</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">マスクメールを作成</string>
|
||||||
|
<string name="create_email_prefix_label">メールプレフィックス(任意)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">例:mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">最大64文字:小文字、数字、アンダースコア</string>
|
||||||
|
<string name="create_email_domain_label">関連ドメイン(任意)</string>
|
||||||
|
<string name="create_email_domain_placeholder">例:example.com</string>
|
||||||
|
<string name="create_email_description_label">説明(任意)</string>
|
||||||
|
<string name="create_email_description_placeholder">例:ショッピングアカウント</string>
|
||||||
|
<string name="create_email_url_label">URL(任意)</string>
|
||||||
|
<string name="create_email_url_placeholder">例:https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">初期状態</string>
|
||||||
|
<string name="create_email_button">マスクメールを作成</string>
|
||||||
|
<string name="create_email_created">作成完了:%s</string>
|
||||||
|
<string name="create_email_copy_action">コピー</string>
|
||||||
|
<string name="navigate_back">戻る</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">メール詳細</string>
|
||||||
|
<string name="email_detail_copy_email">メールアドレスをコピー</string>
|
||||||
|
<string name="email_detail_delete">メールを削除</string>
|
||||||
|
<string name="email_detail_status">状態:</string>
|
||||||
|
<string name="email_detail_created_by">作成者:%s</string>
|
||||||
|
<string name="email_detail_created">作成日:%s</string>
|
||||||
|
<string name="email_detail_last_message">最後のメッセージ:%s</string>
|
||||||
|
<string name="email_detail_enable">有効にする</string>
|
||||||
|
<string name="email_detail_disable">無効にする</string>
|
||||||
|
<string name="email_detail_edit_title">詳細を編集</string>
|
||||||
|
<string name="email_detail_description_label">説明</string>
|
||||||
|
<string name="email_detail_domain_label">関連ドメイン</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">変更を保存</string>
|
||||||
|
<string name="email_detail_updated">更新しました</string>
|
||||||
|
<string name="email_detail_deleted">削除しました</string>
|
||||||
|
<string name="email_detail_copied">クリップボードにコピーしました</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">マスクメールを削除</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">このマスクメールを削除してもよろしいですか?この操作は元に戻せません。</string>
|
||||||
|
<string name="email_detail_delete_confirm">削除</string>
|
||||||
|
<string name="email_detail_delete_cancel">キャンセル</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">有効</string>
|
||||||
|
<string name="state_disabled">無効</string>
|
||||||
|
<string name="state_deleted">削除済み</string>
|
||||||
|
<string name="state_pending">保留中</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">メールの読み込みに失敗しました</string>
|
||||||
|
<string name="error_retry">再試行</string>
|
||||||
|
<string name="error_generic">エラーが発生しました</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">設定</string>
|
||||||
|
<string name="settings_language">言語</string>
|
||||||
|
<string name="settings_language_description">アプリの言語を選択</string>
|
||||||
|
<string name="settings_system_default">システムの既定</string>
|
||||||
|
<string name="settings_contact">お問い合わせとフィードバック</string>
|
||||||
|
<string name="settings_contact_description">フィードバックを送信</string>
|
||||||
|
<string name="settings_logout">ログアウト</string>
|
||||||
|
<string name="settings_logout_description">アカウントからサインアウト</string>
|
||||||
|
<string name="settings_version">バージョン %s</string>
|
||||||
|
<string name="settings_select_language">言語を選択</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask フィードバック</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-ko/strings.xml
Normal file
121
app/src/main/res/values-ko/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail 마스크 이메일 관리자</string>
|
||||||
|
<string name="login_api_token_label">API 토큰</string>
|
||||||
|
<string name="login_api_token_placeholder">Fastmail API 토큰을 입력하세요</string>
|
||||||
|
<string name="login_button">로그인</string>
|
||||||
|
<string name="login_show_token">토큰 표시</string>
|
||||||
|
<string name="login_hide_token">토큰 숨기기</string>
|
||||||
|
<string name="login_instructions_title">API 토큰 얻는 방법:</string>
|
||||||
|
<string name="login_instructions_step1">1. Fastmail 웹 앱에 로그인</string>
|
||||||
|
<string name="login_instructions_step2">2. 설정 > 개인정보 및 보안으로 이동</string>
|
||||||
|
<string name="login_instructions_step3">3. 통합 > API 토큰 클릭</string>
|
||||||
|
<string name="login_instructions_step4">4. \"Masked Email\" 범위로 새 토큰 생성</string>
|
||||||
|
<string name="login_instructions_step5">5. 토큰을 복사하여 위에 붙여넣기</string>
|
||||||
|
<string name="login_instructions_full">1. Fastmail 웹 앱에 로그인\n2. 설정 > 개인정보 및 보안으로 이동\n3. 통합 > API 토큰 클릭\n4. \"Masked Email\" 범위로 새 토큰 생성\n5. 토큰을 복사하여 위에 붙여넣기</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">마스크 이메일</string>
|
||||||
|
<string name="email_list_search_placeholder">이메일 검색…</string>
|
||||||
|
<string name="email_list_clear_search">검색 지우기</string>
|
||||||
|
<string name="email_list_filter_emails">이메일 필터</string>
|
||||||
|
<string name="email_list_create_fab">생성</string>
|
||||||
|
<string name="email_list_create_description">새 마스크 이메일 생성</string>
|
||||||
|
<string name="email_list_empty">마스크 이메일을 찾을 수 없습니다</string>
|
||||||
|
<string name="email_list_logout">로그아웃</string>
|
||||||
|
<string name="email_list_settings">설정</string>
|
||||||
|
<string name="filter_all">전체</string>
|
||||||
|
<string name="filter_enabled">활성화됨</string>
|
||||||
|
<string name="filter_disabled">비활성화됨</string>
|
||||||
|
<string name="filter_deleted">삭제됨</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">마스크 이메일 생성</string>
|
||||||
|
<string name="create_email_prefix_label">이메일 접두사 (선택)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">예: mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">최대 64자: 소문자, 숫자, 밑줄</string>
|
||||||
|
<string name="create_email_domain_label">연결된 도메인 (선택)</string>
|
||||||
|
<string name="create_email_domain_placeholder">예: example.com</string>
|
||||||
|
<string name="create_email_description_label">설명 (선택)</string>
|
||||||
|
<string name="create_email_description_placeholder">예: 쇼핑 계정</string>
|
||||||
|
<string name="create_email_url_label">URL (선택)</string>
|
||||||
|
<string name="create_email_url_placeholder">예: https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">초기 상태</string>
|
||||||
|
<string name="create_email_button">마스크 이메일 생성</string>
|
||||||
|
<string name="create_email_created">생성됨: %s</string>
|
||||||
|
<string name="create_email_copy_action">복사</string>
|
||||||
|
<string name="navigate_back">뒤로</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">이메일 상세</string>
|
||||||
|
<string name="email_detail_copy_email">이메일 주소 복사</string>
|
||||||
|
<string name="email_detail_delete">이메일 삭제</string>
|
||||||
|
<string name="email_detail_status">상태:</string>
|
||||||
|
<string name="email_detail_created_by">생성자: %s</string>
|
||||||
|
<string name="email_detail_created">생성일: %s</string>
|
||||||
|
<string name="email_detail_last_message">마지막 메시지: %s</string>
|
||||||
|
<string name="email_detail_enable">활성화</string>
|
||||||
|
<string name="email_detail_disable">비활성화</string>
|
||||||
|
<string name="email_detail_edit_title">상세 정보 편집</string>
|
||||||
|
<string name="email_detail_description_label">설명</string>
|
||||||
|
<string name="email_detail_domain_label">연결된 도메인</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">변경 사항 저장</string>
|
||||||
|
<string name="email_detail_updated">업데이트 완료</string>
|
||||||
|
<string name="email_detail_deleted">삭제 완료</string>
|
||||||
|
<string name="email_detail_copied">클립보드에 복사됨</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">마스크 이메일 삭제</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">이 마스크 이메일을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.</string>
|
||||||
|
<string name="email_detail_delete_confirm">삭제</string>
|
||||||
|
<string name="email_detail_delete_cancel">취소</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">활성화됨</string>
|
||||||
|
<string name="state_disabled">비활성화됨</string>
|
||||||
|
<string name="state_deleted">삭제됨</string>
|
||||||
|
<string name="state_pending">대기 중</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">이메일 로드 실패</string>
|
||||||
|
<string name="error_retry">다시 시도</string>
|
||||||
|
<string name="error_generic">오류가 발생했습니다</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">설정</string>
|
||||||
|
<string name="settings_language">언어</string>
|
||||||
|
<string name="settings_language_description">앱 언어 선택</string>
|
||||||
|
<string name="settings_system_default">시스템 기본값</string>
|
||||||
|
<string name="settings_contact">연락처 및 피드백</string>
|
||||||
|
<string name="settings_contact_description">피드백을 보내주세요</string>
|
||||||
|
<string name="settings_logout">로그아웃</string>
|
||||||
|
<string name="settings_logout_description">계정에서 로그아웃</string>
|
||||||
|
<string name="settings_version">버전 %s</string>
|
||||||
|
<string name="settings_select_language">언어 선택</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask 피드백</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-nl/strings.xml
Normal file
121
app/src/main/res/values-nl/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail Gemaskeerde E-mail Beheerder</string>
|
||||||
|
<string name="login_api_token_label">API-token</string>
|
||||||
|
<string name="login_api_token_placeholder">Voer uw Fastmail API-token in</string>
|
||||||
|
<string name="login_button">Inloggen</string>
|
||||||
|
<string name="login_show_token">Token tonen</string>
|
||||||
|
<string name="login_hide_token">Token verbergen</string>
|
||||||
|
<string name="login_instructions_title">Hoe u uw API-token krijgt:</string>
|
||||||
|
<string name="login_instructions_step1">1. Log in op de Fastmail web-app</string>
|
||||||
|
<string name="login_instructions_step2">2. Ga naar Instellingen > Privacy en Beveiliging</string>
|
||||||
|
<string name="login_instructions_step3">3. Klik op Integraties > API-tokens</string>
|
||||||
|
<string name="login_instructions_step4">4. Maak een nieuw token met \"Masked Email\" scope</string>
|
||||||
|
<string name="login_instructions_step5">5. Kopieer het token en plak het hierboven</string>
|
||||||
|
<string name="login_instructions_full">1. Log in op de Fastmail web-app\n2. Ga naar Instellingen > Privacy en Beveiliging\n3. Klik op Integraties > API-tokens\n4. Maak een nieuw token met \"Masked Email\" scope\n5. Kopieer het token en plak het hierboven</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Gemaskeerde e-mails</string>
|
||||||
|
<string name="email_list_search_placeholder">E-mails zoeken…</string>
|
||||||
|
<string name="email_list_clear_search">Zoekopdracht wissen</string>
|
||||||
|
<string name="email_list_filter_emails">E-mails filteren</string>
|
||||||
|
<string name="email_list_create_fab">Aanmaken</string>
|
||||||
|
<string name="email_list_create_description">Nieuwe gemaskeerde e-mail aanmaken</string>
|
||||||
|
<string name="email_list_empty">Geen gemaskeerde e-mails gevonden</string>
|
||||||
|
<string name="email_list_logout">Uitloggen</string>
|
||||||
|
<string name="email_list_settings">Instellingen</string>
|
||||||
|
<string name="filter_all">Alle</string>
|
||||||
|
<string name="filter_enabled">Ingeschakeld</string>
|
||||||
|
<string name="filter_disabled">Uitgeschakeld</string>
|
||||||
|
<string name="filter_deleted">Verwijderd</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Gemaskeerde e-mail aanmaken</string>
|
||||||
|
<string name="create_email_prefix_label">E-mail prefix (optioneel)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">bijv. mijnsite_winkelen</string>
|
||||||
|
<string name="create_email_prefix_hint">Max 64 tekens: kleine letters, cijfers, underscores</string>
|
||||||
|
<string name="create_email_domain_label">Gekoppeld domein (optioneel)</string>
|
||||||
|
<string name="create_email_domain_placeholder">bijv. voorbeeld.nl</string>
|
||||||
|
<string name="create_email_description_label">Beschrijving (optioneel)</string>
|
||||||
|
<string name="create_email_description_placeholder">bijv. Winkelaccount</string>
|
||||||
|
<string name="create_email_url_label">URL (optioneel)</string>
|
||||||
|
<string name="create_email_url_placeholder">bijv. https://voorbeeld.nl</string>
|
||||||
|
<string name="create_email_initial_state">Beginstatus</string>
|
||||||
|
<string name="create_email_button">Gemaskeerde e-mail aanmaken</string>
|
||||||
|
<string name="create_email_created">Aangemaakt: %s</string>
|
||||||
|
<string name="create_email_copy_action">Kopiëren</string>
|
||||||
|
<string name="navigate_back">Terug</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">E-maildetails</string>
|
||||||
|
<string name="email_detail_copy_email">E-mailadres kopiëren</string>
|
||||||
|
<string name="email_detail_delete">E-mail verwijderen</string>
|
||||||
|
<string name="email_detail_status">Status:</string>
|
||||||
|
<string name="email_detail_created_by">Aangemaakt door: %s</string>
|
||||||
|
<string name="email_detail_created">Aangemaakt: %s</string>
|
||||||
|
<string name="email_detail_last_message">Laatste bericht: %s</string>
|
||||||
|
<string name="email_detail_enable">Inschakelen</string>
|
||||||
|
<string name="email_detail_disable">Uitschakelen</string>
|
||||||
|
<string name="email_detail_edit_title">Details bewerken</string>
|
||||||
|
<string name="email_detail_description_label">Beschrijving</string>
|
||||||
|
<string name="email_detail_domain_label">Gekoppeld domein</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Wijzigingen opslaan</string>
|
||||||
|
<string name="email_detail_updated">Succesvol bijgewerkt</string>
|
||||||
|
<string name="email_detail_deleted">Succesvol verwijderd</string>
|
||||||
|
<string name="email_detail_copied">Gekopieerd naar klembord</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Gemaskeerde e-mail verwijderen</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Weet u zeker dat u deze gemaskeerde e-mail wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Verwijderen</string>
|
||||||
|
<string name="email_detail_delete_cancel">Annuleren</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Ingeschakeld</string>
|
||||||
|
<string name="state_disabled">Uitgeschakeld</string>
|
||||||
|
<string name="state_deleted">Verwijderd</string>
|
||||||
|
<string name="state_pending">In afwachting</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Kon e-mails niet laden</string>
|
||||||
|
<string name="error_retry">Opnieuw proberen</string>
|
||||||
|
<string name="error_generic">Er is een fout opgetreden</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Instellingen</string>
|
||||||
|
<string name="settings_language">Taal</string>
|
||||||
|
<string name="settings_language_description">App-taal selecteren</string>
|
||||||
|
<string name="settings_system_default">Systeemstandaard</string>
|
||||||
|
<string name="settings_contact">Contact en feedback</string>
|
||||||
|
<string name="settings_contact_description">Stuur ons uw feedback</string>
|
||||||
|
<string name="settings_logout">Uitloggen</string>
|
||||||
|
<string name="settings_logout_description">Afmelden bij uw account</string>
|
||||||
|
<string name="settings_version">Versie %s</string>
|
||||||
|
<string name="settings_select_language">Taal selecteren</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask Feedback</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-pl/strings.xml
Normal file
121
app/src/main/res/values-pl/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Menedżer ukrytych adresów Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Token API</string>
|
||||||
|
<string name="login_api_token_placeholder">Wprowadź token API Fastmail</string>
|
||||||
|
<string name="login_button">Zaloguj się</string>
|
||||||
|
<string name="login_show_token">Pokaż token</string>
|
||||||
|
<string name="login_hide_token">Ukryj token</string>
|
||||||
|
<string name="login_instructions_title">Jak uzyskać token API:</string>
|
||||||
|
<string name="login_instructions_step1">1. Zaloguj się do aplikacji webowej Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Przejdź do Ustawienia > Prywatność i bezpieczeństwo</string>
|
||||||
|
<string name="login_instructions_step3">3. Kliknij Integracje > Tokeny API</string>
|
||||||
|
<string name="login_instructions_step4">4. Utwórz nowy token z zakresem \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Skopiuj token i wklej go powyżej</string>
|
||||||
|
<string name="login_instructions_full">1. Zaloguj się do aplikacji webowej Fastmail\n2. Przejdź do Ustawienia > Prywatność i bezpieczeństwo\n3. Kliknij Integracje > Tokeny API\n4. Utwórz nowy token z zakresem \"Masked Email\"\n5. Skopiuj token i wklej go powyżej</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Ukryte adresy</string>
|
||||||
|
<string name="email_list_search_placeholder">Szukaj adresów…</string>
|
||||||
|
<string name="email_list_clear_search">Wyczyść wyszukiwanie</string>
|
||||||
|
<string name="email_list_filter_emails">Filtruj adresy</string>
|
||||||
|
<string name="email_list_create_fab">Utwórz</string>
|
||||||
|
<string name="email_list_create_description">Utwórz nowy ukryty adres</string>
|
||||||
|
<string name="email_list_empty">Nie znaleziono ukrytych adresów</string>
|
||||||
|
<string name="email_list_logout">Wyloguj</string>
|
||||||
|
<string name="email_list_settings">Ustawienia</string>
|
||||||
|
<string name="filter_all">Wszystkie</string>
|
||||||
|
<string name="filter_enabled">Aktywne</string>
|
||||||
|
<string name="filter_disabled">Wyłączone</string>
|
||||||
|
<string name="filter_deleted">Usunięte</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Utwórz ukryty adres</string>
|
||||||
|
<string name="create_email_prefix_label">Prefiks adresu (opcjonalnie)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">np. mojastrona_zakupy</string>
|
||||||
|
<string name="create_email_prefix_hint">Maks. 64 znaki: małe litery, cyfry, podkreślenia</string>
|
||||||
|
<string name="create_email_domain_label">Powiązana domena (opcjonalnie)</string>
|
||||||
|
<string name="create_email_domain_placeholder">np. przyklad.pl</string>
|
||||||
|
<string name="create_email_description_label">Opis (opcjonalnie)</string>
|
||||||
|
<string name="create_email_description_placeholder">np. Konto zakupowe</string>
|
||||||
|
<string name="create_email_url_label">URL (opcjonalnie)</string>
|
||||||
|
<string name="create_email_url_placeholder">np. https://przyklad.pl</string>
|
||||||
|
<string name="create_email_initial_state">Stan początkowy</string>
|
||||||
|
<string name="create_email_button">Utwórz ukryty adres</string>
|
||||||
|
<string name="create_email_created">Utworzono: %s</string>
|
||||||
|
<string name="create_email_copy_action">Kopiuj</string>
|
||||||
|
<string name="navigate_back">Wstecz</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Szczegóły adresu</string>
|
||||||
|
<string name="email_detail_copy_email">Kopiuj adres e-mail</string>
|
||||||
|
<string name="email_detail_delete">Usuń adres</string>
|
||||||
|
<string name="email_detail_status">Status:</string>
|
||||||
|
<string name="email_detail_created_by">Utworzony przez: %s</string>
|
||||||
|
<string name="email_detail_created">Utworzono: %s</string>
|
||||||
|
<string name="email_detail_last_message">Ostatnia wiadomość: %s</string>
|
||||||
|
<string name="email_detail_enable">Włącz</string>
|
||||||
|
<string name="email_detail_disable">Wyłącz</string>
|
||||||
|
<string name="email_detail_edit_title">Edytuj szczegóły</string>
|
||||||
|
<string name="email_detail_description_label">Opis</string>
|
||||||
|
<string name="email_detail_domain_label">Powiązana domena</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Zapisz zmiany</string>
|
||||||
|
<string name="email_detail_updated">Zaktualizowano pomyślnie</string>
|
||||||
|
<string name="email_detail_deleted">Usunięto pomyślnie</string>
|
||||||
|
<string name="email_detail_copied">Skopiowano do schowka</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Usuń ukryty adres</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Czy na pewno chcesz usunąć ten ukryty adres? Tej operacji nie można cofnąć.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Usuń</string>
|
||||||
|
<string name="email_detail_delete_cancel">Anuluj</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Aktywny</string>
|
||||||
|
<string name="state_disabled">Wyłączony</string>
|
||||||
|
<string name="state_deleted">Usunięty</string>
|
||||||
|
<string name="state_pending">Oczekujący</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Nie udało się załadować adresów</string>
|
||||||
|
<string name="error_retry">Ponów próbę</string>
|
||||||
|
<string name="error_generic">Wystąpił błąd</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Ustawienia</string>
|
||||||
|
<string name="settings_language">Język</string>
|
||||||
|
<string name="settings_language_description">Wybierz język aplikacji</string>
|
||||||
|
<string name="settings_system_default">Domyślny systemowy</string>
|
||||||
|
<string name="settings_contact">Kontakt i opinie</string>
|
||||||
|
<string name="settings_contact_description">Wyślij nam swoją opinię</string>
|
||||||
|
<string name="settings_logout">Wyloguj</string>
|
||||||
|
<string name="settings_logout_description">Wyloguj się z konta</string>
|
||||||
|
<string name="settings_version">Wersja %s</string>
|
||||||
|
<string name="settings_select_language">Wybierz język</string>
|
||||||
|
<string name="settings_feedback_subject">Opinia o FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-pt/strings.xml
Normal file
121
app/src/main/res/values-pt/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Gerenciador de emails mascarados do Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Token de API</string>
|
||||||
|
<string name="login_api_token_placeholder">Digite seu token de API do Fastmail</string>
|
||||||
|
<string name="login_button">Entrar</string>
|
||||||
|
<string name="login_show_token">Mostrar token</string>
|
||||||
|
<string name="login_hide_token">Ocultar token</string>
|
||||||
|
<string name="login_instructions_title">Como obter seu token de API:</string>
|
||||||
|
<string name="login_instructions_step1">1. Faça login no aplicativo web do Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Vá para Configurações > Privacidade e Segurança</string>
|
||||||
|
<string name="login_instructions_step3">3. Clique em Integrações > Tokens de API</string>
|
||||||
|
<string name="login_instructions_step4">4. Crie um novo token com o escopo \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Copie o token e cole acima</string>
|
||||||
|
<string name="login_instructions_full">1. Faça login no aplicativo web do Fastmail\n2. Vá para Configurações > Privacidade e Segurança\n3. Clique em Integrações > Tokens de API\n4. Crie um novo token com o escopo \"Masked Email\"\n5. Copie o token e cole acima</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Emails mascarados</string>
|
||||||
|
<string name="email_list_search_placeholder">Pesquisar emails…</string>
|
||||||
|
<string name="email_list_clear_search">Limpar pesquisa</string>
|
||||||
|
<string name="email_list_filter_emails">Filtrar emails</string>
|
||||||
|
<string name="email_list_create_fab">Criar</string>
|
||||||
|
<string name="email_list_create_description">Criar novo email mascarado</string>
|
||||||
|
<string name="email_list_empty">Nenhum email mascarado encontrado</string>
|
||||||
|
<string name="email_list_logout">Sair</string>
|
||||||
|
<string name="email_list_settings">Configurações</string>
|
||||||
|
<string name="filter_all">Todos</string>
|
||||||
|
<string name="filter_enabled">Ativados</string>
|
||||||
|
<string name="filter_disabled">Desativados</string>
|
||||||
|
<string name="filter_deleted">Excluídos</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Criar email mascarado</string>
|
||||||
|
<string name="create_email_prefix_label">Prefixo do email (opcional)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">ex.: meusite_compras</string>
|
||||||
|
<string name="create_email_prefix_hint">Máx. 64 caracteres: letras minúsculas, números, sublinhados</string>
|
||||||
|
<string name="create_email_domain_label">Domínio associado (opcional)</string>
|
||||||
|
<string name="create_email_domain_placeholder">ex.: exemplo.com</string>
|
||||||
|
<string name="create_email_description_label">Descrição (opcional)</string>
|
||||||
|
<string name="create_email_description_placeholder">ex.: Conta de compras</string>
|
||||||
|
<string name="create_email_url_label">URL (opcional)</string>
|
||||||
|
<string name="create_email_url_placeholder">ex.: https://exemplo.com</string>
|
||||||
|
<string name="create_email_initial_state">Estado inicial</string>
|
||||||
|
<string name="create_email_button">Criar email mascarado</string>
|
||||||
|
<string name="create_email_created">Criado: %s</string>
|
||||||
|
<string name="create_email_copy_action">Copiar</string>
|
||||||
|
<string name="navigate_back">Voltar</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Detalhes do email</string>
|
||||||
|
<string name="email_detail_copy_email">Copiar endereço de email</string>
|
||||||
|
<string name="email_detail_delete">Excluir email</string>
|
||||||
|
<string name="email_detail_status">Status:</string>
|
||||||
|
<string name="email_detail_created_by">Criado por: %s</string>
|
||||||
|
<string name="email_detail_created">Criado: %s</string>
|
||||||
|
<string name="email_detail_last_message">Última mensagem: %s</string>
|
||||||
|
<string name="email_detail_enable">Ativar</string>
|
||||||
|
<string name="email_detail_disable">Desativar</string>
|
||||||
|
<string name="email_detail_edit_title">Editar detalhes</string>
|
||||||
|
<string name="email_detail_description_label">Descrição</string>
|
||||||
|
<string name="email_detail_domain_label">Domínio associado</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Salvar alterações</string>
|
||||||
|
<string name="email_detail_updated">Atualizado com sucesso</string>
|
||||||
|
<string name="email_detail_deleted">Excluído com sucesso</string>
|
||||||
|
<string name="email_detail_copied">Copiado para a área de transferência</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Excluir email mascarado</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Tem certeza de que deseja excluir este email mascarado? Esta ação não pode ser desfeita.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Excluir</string>
|
||||||
|
<string name="email_detail_delete_cancel">Cancelar</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Ativado</string>
|
||||||
|
<string name="state_disabled">Desativado</string>
|
||||||
|
<string name="state_deleted">Excluído</string>
|
||||||
|
<string name="state_pending">Pendente</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Falha ao carregar emails</string>
|
||||||
|
<string name="error_retry">Tentar novamente</string>
|
||||||
|
<string name="error_generic">Ocorreu um erro</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Configurações</string>
|
||||||
|
<string name="settings_language">Idioma</string>
|
||||||
|
<string name="settings_language_description">Selecionar idioma do app</string>
|
||||||
|
<string name="settings_system_default">Padrão do sistema</string>
|
||||||
|
<string name="settings_contact">Contato e feedback</string>
|
||||||
|
<string name="settings_contact_description">Envie-nos seu feedback</string>
|
||||||
|
<string name="settings_logout">Sair</string>
|
||||||
|
<string name="settings_logout_description">Sair da sua conta</string>
|
||||||
|
<string name="settings_version">Versão %s</string>
|
||||||
|
<string name="settings_select_language">Selecionar idioma</string>
|
||||||
|
<string name="settings_feedback_subject">Feedback do FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-ru/strings.xml
Normal file
121
app/src/main/res/values-ru/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Менеджер масок email для Fastmail</string>
|
||||||
|
<string name="login_api_token_label">API токен</string>
|
||||||
|
<string name="login_api_token_placeholder">Введите ваш API токен Fastmail</string>
|
||||||
|
<string name="login_button">Войти</string>
|
||||||
|
<string name="login_show_token">Показать токен</string>
|
||||||
|
<string name="login_hide_token">Скрыть токен</string>
|
||||||
|
<string name="login_instructions_title">Как получить API токен:</string>
|
||||||
|
<string name="login_instructions_step1">1. Войдите в веб-приложение Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Перейдите в Настройки > Конфиденциальность и безопасность</string>
|
||||||
|
<string name="login_instructions_step3">3. Нажмите Интеграции > API токены</string>
|
||||||
|
<string name="login_instructions_step4">4. Создайте новый токен с правами \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Скопируйте токен и вставьте выше</string>
|
||||||
|
<string name="login_instructions_full">1. Войдите в веб-приложение Fastmail\n2. Перейдите в Настройки > Конфиденциальность и безопасность\n3. Нажмите Интеграции > API токены\n4. Создайте новый токен с правами \"Masked Email\"\n5. Скопируйте токен и вставьте выше</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Маски email</string>
|
||||||
|
<string name="email_list_search_placeholder">Поиск email…</string>
|
||||||
|
<string name="email_list_clear_search">Очистить поиск</string>
|
||||||
|
<string name="email_list_filter_emails">Фильтровать email</string>
|
||||||
|
<string name="email_list_create_fab">Создать</string>
|
||||||
|
<string name="email_list_create_description">Создать новую маску email</string>
|
||||||
|
<string name="email_list_empty">Маски email не найдены</string>
|
||||||
|
<string name="email_list_logout">Выйти</string>
|
||||||
|
<string name="email_list_settings">Настройки</string>
|
||||||
|
<string name="filter_all">Все</string>
|
||||||
|
<string name="filter_enabled">Активные</string>
|
||||||
|
<string name="filter_disabled">Отключённые</string>
|
||||||
|
<string name="filter_deleted">Удалённые</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Создать маску email</string>
|
||||||
|
<string name="create_email_prefix_label">Префикс email (необязательно)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">напр., mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">Макс. 64 символа: строчные буквы, цифры, подчёркивания</string>
|
||||||
|
<string name="create_email_domain_label">Связанный домен (необязательно)</string>
|
||||||
|
<string name="create_email_domain_placeholder">напр., example.com</string>
|
||||||
|
<string name="create_email_description_label">Описание (необязательно)</string>
|
||||||
|
<string name="create_email_description_placeholder">напр., Аккаунт для покупок</string>
|
||||||
|
<string name="create_email_url_label">URL (необязательно)</string>
|
||||||
|
<string name="create_email_url_placeholder">напр., https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">Начальное состояние</string>
|
||||||
|
<string name="create_email_button">Создать маску email</string>
|
||||||
|
<string name="create_email_created">Создано: %s</string>
|
||||||
|
<string name="create_email_copy_action">Копировать</string>
|
||||||
|
<string name="navigate_back">Назад</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Детали email</string>
|
||||||
|
<string name="email_detail_copy_email">Копировать адрес email</string>
|
||||||
|
<string name="email_detail_delete">Удалить email</string>
|
||||||
|
<string name="email_detail_status">Статус:</string>
|
||||||
|
<string name="email_detail_created_by">Создано: %s</string>
|
||||||
|
<string name="email_detail_created">Создано: %s</string>
|
||||||
|
<string name="email_detail_last_message">Последнее сообщение: %s</string>
|
||||||
|
<string name="email_detail_enable">Включить</string>
|
||||||
|
<string name="email_detail_disable">Отключить</string>
|
||||||
|
<string name="email_detail_edit_title">Редактировать</string>
|
||||||
|
<string name="email_detail_description_label">Описание</string>
|
||||||
|
<string name="email_detail_domain_label">Связанный домен</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Сохранить изменения</string>
|
||||||
|
<string name="email_detail_updated">Успешно обновлено</string>
|
||||||
|
<string name="email_detail_deleted">Успешно удалено</string>
|
||||||
|
<string name="email_detail_copied">Скопировано в буфер обмена</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Удалить маску email</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Вы уверены, что хотите удалить эту маску email? Это действие нельзя отменить.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Удалить</string>
|
||||||
|
<string name="email_detail_delete_cancel">Отмена</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Активна</string>
|
||||||
|
<string name="state_disabled">Отключена</string>
|
||||||
|
<string name="state_deleted">Удалена</string>
|
||||||
|
<string name="state_pending">Ожидание</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Не удалось загрузить email</string>
|
||||||
|
<string name="error_retry">Повторить</string>
|
||||||
|
<string name="error_generic">Произошла ошибка</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Настройки</string>
|
||||||
|
<string name="settings_language">Язык</string>
|
||||||
|
<string name="settings_language_description">Выбрать язык приложения</string>
|
||||||
|
<string name="settings_system_default">Системный по умолчанию</string>
|
||||||
|
<string name="settings_contact">Связаться и оставить отзыв</string>
|
||||||
|
<string name="settings_contact_description">Отправьте нам отзыв</string>
|
||||||
|
<string name="settings_logout">Выйти</string>
|
||||||
|
<string name="settings_logout_description">Выйти из аккаунта</string>
|
||||||
|
<string name="settings_version">Версия %s</string>
|
||||||
|
<string name="settings_select_language">Выбрать язык</string>
|
||||||
|
<string name="settings_feedback_subject">Отзыв о FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-th/strings.xml
Normal file
121
app/src/main/res/values-th/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">ตัวจัดการอีเมลซ่อน Fastmail</string>
|
||||||
|
<string name="login_api_token_label">โทเค็น API</string>
|
||||||
|
<string name="login_api_token_placeholder">ใส่โทเค็น API ของ Fastmail</string>
|
||||||
|
<string name="login_button">เข้าสู่ระบบ</string>
|
||||||
|
<string name="login_show_token">แสดงโทเค็น</string>
|
||||||
|
<string name="login_hide_token">ซ่อนโทเค็น</string>
|
||||||
|
<string name="login_instructions_title">วิธีรับโทเค็น API:</string>
|
||||||
|
<string name="login_instructions_step1">1. เข้าสู่ระบบแอป Fastmail บนเว็บ</string>
|
||||||
|
<string name="login_instructions_step2">2. ไปที่ ตั้งค่า > ความเป็นส่วนตัวและความปลอดภัย</string>
|
||||||
|
<string name="login_instructions_step3">3. คลิก การเชื่อมต่อ > โทเค็น API</string>
|
||||||
|
<string name="login_instructions_step4">4. สร้างโทเค็นใหม่ที่มีขอบเขต \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. คัดลอกโทเค็นและวางด้านบน</string>
|
||||||
|
<string name="login_instructions_full">1. เข้าสู่ระบบแอป Fastmail บนเว็บ\n2. ไปที่ ตั้งค่า > ความเป็นส่วนตัวและความปลอดภัย\n3. คลิก การเชื่อมต่อ > โทเค็น API\n4. สร้างโทเค็นใหม่ที่มีขอบเขต \"Masked Email\"\n5. คัดลอกโทเค็นและวางด้านบน</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">อีเมลซ่อน</string>
|
||||||
|
<string name="email_list_search_placeholder">ค้นหาอีเมล…</string>
|
||||||
|
<string name="email_list_clear_search">ล้างการค้นหา</string>
|
||||||
|
<string name="email_list_filter_emails">กรองอีเมล</string>
|
||||||
|
<string name="email_list_create_fab">สร้าง</string>
|
||||||
|
<string name="email_list_create_description">สร้างอีเมลซ่อนใหม่</string>
|
||||||
|
<string name="email_list_empty">ไม่พบอีเมลซ่อน</string>
|
||||||
|
<string name="email_list_logout">ออกจากระบบ</string>
|
||||||
|
<string name="email_list_settings">การตั้งค่า</string>
|
||||||
|
<string name="filter_all">ทั้งหมด</string>
|
||||||
|
<string name="filter_enabled">เปิดใช้งาน</string>
|
||||||
|
<string name="filter_disabled">ปิดใช้งาน</string>
|
||||||
|
<string name="filter_deleted">ลบแล้ว</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">สร้างอีเมลซ่อน</string>
|
||||||
|
<string name="create_email_prefix_label">คำนำหน้าอีเมล (ไม่บังคับ)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">เช่น mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">สูงสุด 64 ตัวอักษร: ตัวพิมพ์เล็ก, ตัวเลข, ขีดล่าง</string>
|
||||||
|
<string name="create_email_domain_label">โดเมนที่เกี่ยวข้อง (ไม่บังคับ)</string>
|
||||||
|
<string name="create_email_domain_placeholder">เช่น example.com</string>
|
||||||
|
<string name="create_email_description_label">คำอธิบาย (ไม่บังคับ)</string>
|
||||||
|
<string name="create_email_description_placeholder">เช่น บัญชีช้อปปิ้ง</string>
|
||||||
|
<string name="create_email_url_label">URL (ไม่บังคับ)</string>
|
||||||
|
<string name="create_email_url_placeholder">เช่น https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">สถานะเริ่มต้น</string>
|
||||||
|
<string name="create_email_button">สร้างอีเมลซ่อน</string>
|
||||||
|
<string name="create_email_created">สร้างแล้ว: %s</string>
|
||||||
|
<string name="create_email_copy_action">คัดลอก</string>
|
||||||
|
<string name="navigate_back">กลับ</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">รายละเอียดอีเมล</string>
|
||||||
|
<string name="email_detail_copy_email">คัดลอกที่อยู่อีเมล</string>
|
||||||
|
<string name="email_detail_delete">ลบอีเมล</string>
|
||||||
|
<string name="email_detail_status">สถานะ:</string>
|
||||||
|
<string name="email_detail_created_by">สร้างโดย: %s</string>
|
||||||
|
<string name="email_detail_created">สร้างเมื่อ: %s</string>
|
||||||
|
<string name="email_detail_last_message">ข้อความล่าสุด: %s</string>
|
||||||
|
<string name="email_detail_enable">เปิดใช้งาน</string>
|
||||||
|
<string name="email_detail_disable">ปิดใช้งาน</string>
|
||||||
|
<string name="email_detail_edit_title">แก้ไขรายละเอียด</string>
|
||||||
|
<string name="email_detail_description_label">คำอธิบาย</string>
|
||||||
|
<string name="email_detail_domain_label">โดเมนที่เกี่ยวข้อง</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">บันทึกการเปลี่ยนแปลง</string>
|
||||||
|
<string name="email_detail_updated">อัปเดตสำเร็จ</string>
|
||||||
|
<string name="email_detail_deleted">ลบสำเร็จ</string>
|
||||||
|
<string name="email_detail_copied">คัดลอกไปยังคลิปบอร์ดแล้ว</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">ลบอีเมลซ่อน</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">คุณแน่ใจหรือไม่ว่าต้องการลบอีเมลซ่อนนี้? การกระทำนี้ไม่สามารถย้อนกลับได้</string>
|
||||||
|
<string name="email_detail_delete_confirm">ลบ</string>
|
||||||
|
<string name="email_detail_delete_cancel">ยกเลิก</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">เปิดใช้งาน</string>
|
||||||
|
<string name="state_disabled">ปิดใช้งาน</string>
|
||||||
|
<string name="state_deleted">ลบแล้ว</string>
|
||||||
|
<string name="state_pending">รอดำเนินการ</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">ไม่สามารถโหลดอีเมลได้</string>
|
||||||
|
<string name="error_retry">ลองอีกครั้ง</string>
|
||||||
|
<string name="error_generic">เกิดข้อผิดพลาด</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">การตั้งค่า</string>
|
||||||
|
<string name="settings_language">ภาษา</string>
|
||||||
|
<string name="settings_language_description">เลือกภาษาแอป</string>
|
||||||
|
<string name="settings_system_default">ค่าเริ่มต้นของระบบ</string>
|
||||||
|
<string name="settings_contact">ติดต่อและความคิดเห็น</string>
|
||||||
|
<string name="settings_contact_description">ส่งความคิดเห็นให้เรา</string>
|
||||||
|
<string name="settings_logout">ออกจากระบบ</string>
|
||||||
|
<string name="settings_logout_description">ออกจากบัญชีของคุณ</string>
|
||||||
|
<string name="settings_version">เวอร์ชัน %s</string>
|
||||||
|
<string name="settings_select_language">เลือกภาษา</string>
|
||||||
|
<string name="settings_feedback_subject">ความคิดเห็น FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-tr/strings.xml
Normal file
121
app/src/main/res/values-tr/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail Maskeli E-posta Yöneticisi</string>
|
||||||
|
<string name="login_api_token_label">API Jetonu</string>
|
||||||
|
<string name="login_api_token_placeholder">Fastmail API jetonunuzu girin</string>
|
||||||
|
<string name="login_button">Giriş Yap</string>
|
||||||
|
<string name="login_show_token">Jetonu göster</string>
|
||||||
|
<string name="login_hide_token">Jetonu gizle</string>
|
||||||
|
<string name="login_instructions_title">API jetonunuzu nasıl alırsınız:</string>
|
||||||
|
<string name="login_instructions_step1">1. Fastmail web uygulamasına giriş yapın</string>
|
||||||
|
<string name="login_instructions_step2">2. Ayarlar > Gizlilik ve Güvenlik\'e gidin</string>
|
||||||
|
<string name="login_instructions_step3">3. Entegrasyonlar > API Jetonları\'na tıklayın</string>
|
||||||
|
<string name="login_instructions_step4">4. \"Masked Email\" kapsamında yeni bir jeton oluşturun</string>
|
||||||
|
<string name="login_instructions_step5">5. Jetonu kopyalayın ve yukarıya yapıştırın</string>
|
||||||
|
<string name="login_instructions_full">1. Fastmail web uygulamasına giriş yapın\n2. Ayarlar > Gizlilik ve Güvenlik\'e gidin\n3. Entegrasyonlar > API Jetonları\'na tıklayın\n4. \"Masked Email\" kapsamında yeni bir jeton oluşturun\n5. Jetonu kopyalayın ve yukarıya yapıştırın</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Maskeli E-postalar</string>
|
||||||
|
<string name="email_list_search_placeholder">E-posta ara…</string>
|
||||||
|
<string name="email_list_clear_search">Aramayı temizle</string>
|
||||||
|
<string name="email_list_filter_emails">E-postaları filtrele</string>
|
||||||
|
<string name="email_list_create_fab">Oluştur</string>
|
||||||
|
<string name="email_list_create_description">Yeni maskeli e-posta oluştur</string>
|
||||||
|
<string name="email_list_empty">Maskeli e-posta bulunamadı</string>
|
||||||
|
<string name="email_list_logout">Çıkış Yap</string>
|
||||||
|
<string name="email_list_settings">Ayarlar</string>
|
||||||
|
<string name="filter_all">Tümü</string>
|
||||||
|
<string name="filter_enabled">Etkin</string>
|
||||||
|
<string name="filter_disabled">Devre Dışı</string>
|
||||||
|
<string name="filter_deleted">Silinmiş</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Maskeli E-posta Oluştur</string>
|
||||||
|
<string name="create_email_prefix_label">E-posta öneki (isteğe bağlı)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">örn. sitem_alisveris</string>
|
||||||
|
<string name="create_email_prefix_hint">Maks 64 karakter: küçük harfler, rakamlar, alt çizgiler</string>
|
||||||
|
<string name="create_email_domain_label">İlişkili alan adı (isteğe bağlı)</string>
|
||||||
|
<string name="create_email_domain_placeholder">örn. ornek.com</string>
|
||||||
|
<string name="create_email_description_label">Açıklama (isteğe bağlı)</string>
|
||||||
|
<string name="create_email_description_placeholder">örn. Alışveriş hesabı</string>
|
||||||
|
<string name="create_email_url_label">URL (isteğe bağlı)</string>
|
||||||
|
<string name="create_email_url_placeholder">örn. https://ornek.com</string>
|
||||||
|
<string name="create_email_initial_state">Başlangıç durumu</string>
|
||||||
|
<string name="create_email_button">Maskeli E-posta Oluştur</string>
|
||||||
|
<string name="create_email_created">Oluşturuldu: %s</string>
|
||||||
|
<string name="create_email_copy_action">Kopyala</string>
|
||||||
|
<string name="navigate_back">Geri</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">E-posta Detayları</string>
|
||||||
|
<string name="email_detail_copy_email">E-posta adresini kopyala</string>
|
||||||
|
<string name="email_detail_delete">E-postayı sil</string>
|
||||||
|
<string name="email_detail_status">Durum:</string>
|
||||||
|
<string name="email_detail_created_by">Oluşturan: %s</string>
|
||||||
|
<string name="email_detail_created">Oluşturulma: %s</string>
|
||||||
|
<string name="email_detail_last_message">Son mesaj: %s</string>
|
||||||
|
<string name="email_detail_enable">Etkinleştir</string>
|
||||||
|
<string name="email_detail_disable">Devre dışı bırak</string>
|
||||||
|
<string name="email_detail_edit_title">Detayları Düzenle</string>
|
||||||
|
<string name="email_detail_description_label">Açıklama</string>
|
||||||
|
<string name="email_detail_domain_label">İlişkili alan adı</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Değişiklikleri Kaydet</string>
|
||||||
|
<string name="email_detail_updated">Başarıyla güncellendi</string>
|
||||||
|
<string name="email_detail_deleted">Başarıyla silindi</string>
|
||||||
|
<string name="email_detail_copied">Panoya kopyalandı</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Maskeli E-postayı Sil</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Bu maskeli e-postayı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Sil</string>
|
||||||
|
<string name="email_detail_delete_cancel">İptal</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Etkin</string>
|
||||||
|
<string name="state_disabled">Devre Dışı</string>
|
||||||
|
<string name="state_deleted">Silinmiş</string>
|
||||||
|
<string name="state_pending">Beklemede</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">E-postalar yüklenemedi</string>
|
||||||
|
<string name="error_retry">Tekrar Dene</string>
|
||||||
|
<string name="error_generic">Bir hata oluştu</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Ayarlar</string>
|
||||||
|
<string name="settings_language">Dil</string>
|
||||||
|
<string name="settings_language_description">Uygulama dilini seçin</string>
|
||||||
|
<string name="settings_system_default">Sistem Varsayılanı</string>
|
||||||
|
<string name="settings_contact">İletişim ve Geri Bildirim</string>
|
||||||
|
<string name="settings_contact_description">Geri bildiriminizi gönderin</string>
|
||||||
|
<string name="settings_logout">Çıkış Yap</string>
|
||||||
|
<string name="settings_logout_description">Hesabınızdan çıkış yapın</string>
|
||||||
|
<string name="settings_version">Sürüm %s</string>
|
||||||
|
<string name="settings_select_language">Dil Seçin</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask Geri Bildirimi</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-uk/strings.xml
Normal file
121
app/src/main/res/values-uk/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Менеджер масок email для Fastmail</string>
|
||||||
|
<string name="login_api_token_label">API токен</string>
|
||||||
|
<string name="login_api_token_placeholder">Введіть ваш API токен Fastmail</string>
|
||||||
|
<string name="login_button">Увійти</string>
|
||||||
|
<string name="login_show_token">Показати токен</string>
|
||||||
|
<string name="login_hide_token">Приховати токен</string>
|
||||||
|
<string name="login_instructions_title">Як отримати API токен:</string>
|
||||||
|
<string name="login_instructions_step1">1. Увійдіть у веб-застосунок Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Перейдіть до Налаштування > Конфіденційність і безпека</string>
|
||||||
|
<string name="login_instructions_step3">3. Натисніть Інтеграції > API токени</string>
|
||||||
|
<string name="login_instructions_step4">4. Створіть новий токен з правами \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Скопіюйте токен і вставте вище</string>
|
||||||
|
<string name="login_instructions_full">1. Увійдіть у веб-застосунок Fastmail\n2. Перейдіть до Налаштування > Конфіденційність і безпека\n3. Натисніть Інтеграції > API токени\n4. Створіть новий токен з правами \"Masked Email\"\n5. Скопіюйте токен і вставте вище</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Маски email</string>
|
||||||
|
<string name="email_list_search_placeholder">Пошук email…</string>
|
||||||
|
<string name="email_list_clear_search">Очистити пошук</string>
|
||||||
|
<string name="email_list_filter_emails">Фільтрувати email</string>
|
||||||
|
<string name="email_list_create_fab">Створити</string>
|
||||||
|
<string name="email_list_create_description">Створити нову маску email</string>
|
||||||
|
<string name="email_list_empty">Маски email не знайдено</string>
|
||||||
|
<string name="email_list_logout">Вийти</string>
|
||||||
|
<string name="email_list_settings">Налаштування</string>
|
||||||
|
<string name="filter_all">Усі</string>
|
||||||
|
<string name="filter_enabled">Активні</string>
|
||||||
|
<string name="filter_disabled">Вимкнені</string>
|
||||||
|
<string name="filter_deleted">Видалені</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Створити маску email</string>
|
||||||
|
<string name="create_email_prefix_label">Префікс email (необов\'язково)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">напр., mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">Макс. 64 символи: малі літери, цифри, підкреслення</string>
|
||||||
|
<string name="create_email_domain_label">Пов\'язаний домен (необов\'язково)</string>
|
||||||
|
<string name="create_email_domain_placeholder">напр., example.com</string>
|
||||||
|
<string name="create_email_description_label">Опис (необов\'язково)</string>
|
||||||
|
<string name="create_email_description_placeholder">напр., Акаунт для покупок</string>
|
||||||
|
<string name="create_email_url_label">URL (необов\'язково)</string>
|
||||||
|
<string name="create_email_url_placeholder">напр., https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">Початковий стан</string>
|
||||||
|
<string name="create_email_button">Створити маску email</string>
|
||||||
|
<string name="create_email_created">Створено: %s</string>
|
||||||
|
<string name="create_email_copy_action">Копіювати</string>
|
||||||
|
<string name="navigate_back">Назад</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Деталі email</string>
|
||||||
|
<string name="email_detail_copy_email">Копіювати адресу email</string>
|
||||||
|
<string name="email_detail_delete">Видалити email</string>
|
||||||
|
<string name="email_detail_status">Статус:</string>
|
||||||
|
<string name="email_detail_created_by">Створено: %s</string>
|
||||||
|
<string name="email_detail_created">Створено: %s</string>
|
||||||
|
<string name="email_detail_last_message">Останнє повідомлення: %s</string>
|
||||||
|
<string name="email_detail_enable">Увімкнути</string>
|
||||||
|
<string name="email_detail_disable">Вимкнути</string>
|
||||||
|
<string name="email_detail_edit_title">Редагувати</string>
|
||||||
|
<string name="email_detail_description_label">Опис</string>
|
||||||
|
<string name="email_detail_domain_label">Пов\'язаний домен</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Зберегти зміни</string>
|
||||||
|
<string name="email_detail_updated">Успішно оновлено</string>
|
||||||
|
<string name="email_detail_deleted">Успішно видалено</string>
|
||||||
|
<string name="email_detail_copied">Скопійовано до буфера обміну</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Видалити маску email</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Ви впевнені, що хочете видалити цю маску email? Цю дію не можна скасувати.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Видалити</string>
|
||||||
|
<string name="email_detail_delete_cancel">Скасувати</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Активна</string>
|
||||||
|
<string name="state_disabled">Вимкнена</string>
|
||||||
|
<string name="state_deleted">Видалена</string>
|
||||||
|
<string name="state_pending">Очікування</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Не вдалося завантажити email</string>
|
||||||
|
<string name="error_retry">Повторити</string>
|
||||||
|
<string name="error_generic">Сталася помилка</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Налаштування</string>
|
||||||
|
<string name="settings_language">Мова</string>
|
||||||
|
<string name="settings_language_description">Вибрати мову застосунку</string>
|
||||||
|
<string name="settings_system_default">Системна за замовчуванням</string>
|
||||||
|
<string name="settings_contact">Зв\'язок та відгуки</string>
|
||||||
|
<string name="settings_contact_description">Надішліть нам відгук</string>
|
||||||
|
<string name="settings_logout">Вийти</string>
|
||||||
|
<string name="settings_logout_description">Вийти з акаунту</string>
|
||||||
|
<string name="settings_version">Версія %s</string>
|
||||||
|
<string name="settings_select_language">Вибрати мову</string>
|
||||||
|
<string name="settings_feedback_subject">Відгук про FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
15
app/src/main/res/values-v31/themes.xml
Normal file
15
app/src/main/res/values-v31/themes.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="Theme.FastMask" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.FastMask.Splash" parent="Theme.SplashScreen">
|
||||||
|
<item name="windowSplashScreenBackground">@color/splash_background</item>
|
||||||
|
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||||
|
<item name="windowSplashScreenAnimationDuration">500</item>
|
||||||
|
<item name="postSplashScreenTheme">@style/Theme.FastMask</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-vi/strings.xml
Normal file
121
app/src/main/res/values-vi/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Quản lý Email ẩn danh Fastmail</string>
|
||||||
|
<string name="login_api_token_label">Mã API</string>
|
||||||
|
<string name="login_api_token_placeholder">Nhập mã API Fastmail của bạn</string>
|
||||||
|
<string name="login_button">Đăng nhập</string>
|
||||||
|
<string name="login_show_token">Hiện mã</string>
|
||||||
|
<string name="login_hide_token">Ẩn mã</string>
|
||||||
|
<string name="login_instructions_title">Cách lấy mã API:</string>
|
||||||
|
<string name="login_instructions_step1">1. Đăng nhập vào ứng dụng web Fastmail</string>
|
||||||
|
<string name="login_instructions_step2">2. Vào Cài đặt > Quyền riêng tư & Bảo mật</string>
|
||||||
|
<string name="login_instructions_step3">3. Nhấp vào Tích hợp > Mã API</string>
|
||||||
|
<string name="login_instructions_step4">4. Tạo mã mới với phạm vi \"Masked Email\"</string>
|
||||||
|
<string name="login_instructions_step5">5. Sao chép mã và dán ở trên</string>
|
||||||
|
<string name="login_instructions_full">1. Đăng nhập vào ứng dụng web Fastmail\n2. Vào Cài đặt > Quyền riêng tư & Bảo mật\n3. Nhấp vào Tích hợp > Mã API\n4. Tạo mã mới với phạm vi \"Masked Email\"\n5. Sao chép mã và dán ở trên</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Email ẩn danh</string>
|
||||||
|
<string name="email_list_search_placeholder">Tìm kiếm email…</string>
|
||||||
|
<string name="email_list_clear_search">Xóa tìm kiếm</string>
|
||||||
|
<string name="email_list_filter_emails">Lọc email</string>
|
||||||
|
<string name="email_list_create_fab">Tạo mới</string>
|
||||||
|
<string name="email_list_create_description">Tạo email ẩn danh mới</string>
|
||||||
|
<string name="email_list_empty">Không tìm thấy email ẩn danh</string>
|
||||||
|
<string name="email_list_logout">Đăng xuất</string>
|
||||||
|
<string name="email_list_settings">Cài đặt</string>
|
||||||
|
<string name="filter_all">Tất cả</string>
|
||||||
|
<string name="filter_enabled">Đang hoạt động</string>
|
||||||
|
<string name="filter_disabled">Đã tắt</string>
|
||||||
|
<string name="filter_deleted">Đã xóa</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Tạo email ẩn danh</string>
|
||||||
|
<string name="create_email_prefix_label">Tiền tố email (tùy chọn)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">vd: trangweb_muasam</string>
|
||||||
|
<string name="create_email_prefix_hint">Tối đa 64 ký tự: chữ thường, số, gạch dưới</string>
|
||||||
|
<string name="create_email_domain_label">Tên miền liên kết (tùy chọn)</string>
|
||||||
|
<string name="create_email_domain_placeholder">vd: example.com</string>
|
||||||
|
<string name="create_email_description_label">Mô tả (tùy chọn)</string>
|
||||||
|
<string name="create_email_description_placeholder">vd: Tài khoản mua sắm</string>
|
||||||
|
<string name="create_email_url_label">URL (tùy chọn)</string>
|
||||||
|
<string name="create_email_url_placeholder">vd: https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">Trạng thái ban đầu</string>
|
||||||
|
<string name="create_email_button">Tạo email ẩn danh</string>
|
||||||
|
<string name="create_email_created">Đã tạo: %s</string>
|
||||||
|
<string name="create_email_copy_action">Sao chép</string>
|
||||||
|
<string name="navigate_back">Quay lại</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Chi tiết email</string>
|
||||||
|
<string name="email_detail_copy_email">Sao chép địa chỉ email</string>
|
||||||
|
<string name="email_detail_delete">Xóa email</string>
|
||||||
|
<string name="email_detail_status">Trạng thái:</string>
|
||||||
|
<string name="email_detail_created_by">Tạo bởi: %s</string>
|
||||||
|
<string name="email_detail_created">Ngày tạo: %s</string>
|
||||||
|
<string name="email_detail_last_message">Tin nhắn cuối: %s</string>
|
||||||
|
<string name="email_detail_enable">Bật</string>
|
||||||
|
<string name="email_detail_disable">Tắt</string>
|
||||||
|
<string name="email_detail_edit_title">Chỉnh sửa chi tiết</string>
|
||||||
|
<string name="email_detail_description_label">Mô tả</string>
|
||||||
|
<string name="email_detail_domain_label">Tên miền liên kết</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Lưu thay đổi</string>
|
||||||
|
<string name="email_detail_updated">Cập nhật thành công</string>
|
||||||
|
<string name="email_detail_deleted">Xóa thành công</string>
|
||||||
|
<string name="email_detail_copied">Đã sao chép vào clipboard</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Xóa email ẩn danh</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Bạn có chắc muốn xóa email ẩn danh này? Hành động này không thể hoàn tác.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Xóa</string>
|
||||||
|
<string name="email_detail_delete_cancel">Hủy</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Đang hoạt động</string>
|
||||||
|
<string name="state_disabled">Đã tắt</string>
|
||||||
|
<string name="state_deleted">Đã xóa</string>
|
||||||
|
<string name="state_pending">Đang chờ</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Không thể tải email</string>
|
||||||
|
<string name="error_retry">Thử lại</string>
|
||||||
|
<string name="error_generic">Đã xảy ra lỗi</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Cài đặt</string>
|
||||||
|
<string name="settings_language">Ngôn ngữ</string>
|
||||||
|
<string name="settings_language_description">Chọn ngôn ngữ ứng dụng</string>
|
||||||
|
<string name="settings_system_default">Mặc định hệ thống</string>
|
||||||
|
<string name="settings_contact">Liên hệ & Phản hồi</string>
|
||||||
|
<string name="settings_contact_description">Gửi phản hồi cho chúng tôi</string>
|
||||||
|
<string name="settings_logout">Đăng xuất</string>
|
||||||
|
<string name="settings_logout_description">Đăng xuất khỏi tài khoản</string>
|
||||||
|
<string name="settings_version">Phiên bản %s</string>
|
||||||
|
<string name="settings_select_language">Chọn ngôn ngữ</string>
|
||||||
|
<string name="settings_feedback_subject">Phản hồi FastMask</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
121
app/src/main/res/values-zh-rCN/strings.xml
Normal file
121
app/src/main/res/values-zh-rCN/strings.xml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail 隐藏邮箱管理器</string>
|
||||||
|
<string name="login_api_token_label">API 令牌</string>
|
||||||
|
<string name="login_api_token_placeholder">输入您的 Fastmail API 令牌</string>
|
||||||
|
<string name="login_button">登录</string>
|
||||||
|
<string name="login_show_token">显示令牌</string>
|
||||||
|
<string name="login_hide_token">隐藏令牌</string>
|
||||||
|
<string name="login_instructions_title">如何获取您的 API 令牌:</string>
|
||||||
|
<string name="login_instructions_step1">1. 登录 Fastmail 网页版</string>
|
||||||
|
<string name="login_instructions_step2">2. 前往 设置 > 隐私与安全</string>
|
||||||
|
<string name="login_instructions_step3">3. 点击 集成 > API 令牌</string>
|
||||||
|
<string name="login_instructions_step4">4. 创建一个具有"隐藏邮箱"权限的新令牌</string>
|
||||||
|
<string name="login_instructions_step5">5. 复制令牌并粘贴到上方</string>
|
||||||
|
<string name="login_instructions_full">1. 登录 Fastmail 网页版\n2. 前往 设置 > 隐私与安全\n3. 点击 集成 > API 令牌\n4. 创建一个具有"隐藏邮箱"权限的新令牌\n5. 复制令牌并粘贴到上方</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">隐藏邮箱</string>
|
||||||
|
<string name="email_list_search_placeholder">搜索邮箱…</string>
|
||||||
|
<string name="email_list_clear_search">清除搜索</string>
|
||||||
|
<string name="email_list_filter_emails">筛选邮箱</string>
|
||||||
|
<string name="email_list_create_fab">创建</string>
|
||||||
|
<string name="email_list_create_description">创建新的隐藏邮箱</string>
|
||||||
|
<string name="email_list_empty">未找到隐藏邮箱</string>
|
||||||
|
<string name="email_list_logout">退出登录</string>
|
||||||
|
<string name="email_list_settings">设置</string>
|
||||||
|
<string name="filter_all">全部</string>
|
||||||
|
<string name="filter_enabled">已启用</string>
|
||||||
|
<string name="filter_disabled">已禁用</string>
|
||||||
|
<string name="filter_deleted">已删除</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">创建隐藏邮箱</string>
|
||||||
|
<string name="create_email_prefix_label">邮箱前缀(可选)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">例如:mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">最多64个字符:小写字母、数字、下划线</string>
|
||||||
|
<string name="create_email_domain_label">关联域名(可选)</string>
|
||||||
|
<string name="create_email_domain_placeholder">例如:example.com</string>
|
||||||
|
<string name="create_email_description_label">描述(可选)</string>
|
||||||
|
<string name="create_email_description_placeholder">例如:购物账户</string>
|
||||||
|
<string name="create_email_url_label">网址(可选)</string>
|
||||||
|
<string name="create_email_url_placeholder">例如:https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">初始状态</string>
|
||||||
|
<string name="create_email_button">创建隐藏邮箱</string>
|
||||||
|
<string name="create_email_created">已创建:%s</string>
|
||||||
|
<string name="create_email_copy_action">复制</string>
|
||||||
|
<string name="navigate_back">返回</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">邮箱详情</string>
|
||||||
|
<string name="email_detail_copy_email">复制邮箱地址</string>
|
||||||
|
<string name="email_detail_delete">删除邮箱</string>
|
||||||
|
<string name="email_detail_status">状态:</string>
|
||||||
|
<string name="email_detail_created_by">创建者:%s</string>
|
||||||
|
<string name="email_detail_created">创建时间:%s</string>
|
||||||
|
<string name="email_detail_last_message">最后消息:%s</string>
|
||||||
|
<string name="email_detail_enable">启用</string>
|
||||||
|
<string name="email_detail_disable">禁用</string>
|
||||||
|
<string name="email_detail_edit_title">编辑详情</string>
|
||||||
|
<string name="email_detail_description_label">描述</string>
|
||||||
|
<string name="email_detail_domain_label">关联域名</string>
|
||||||
|
<string name="email_detail_url_label">网址</string>
|
||||||
|
<string name="email_detail_save">保存更改</string>
|
||||||
|
<string name="email_detail_updated">更新成功</string>
|
||||||
|
<string name="email_detail_deleted">删除成功</string>
|
||||||
|
<string name="email_detail_copied">已复制到剪贴板</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">删除隐藏邮箱</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">您确定要删除此隐藏邮箱吗?此操作无法撤销。</string>
|
||||||
|
<string name="email_detail_delete_confirm">删除</string>
|
||||||
|
<string name="email_detail_delete_cancel">取消</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">已启用</string>
|
||||||
|
<string name="state_disabled">已禁用</string>
|
||||||
|
<string name="state_deleted">已删除</string>
|
||||||
|
<string name="state_pending">待处理</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">加载邮箱失败</string>
|
||||||
|
<string name="error_retry">重试</string>
|
||||||
|
<string name="error_generic">发生错误</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">设置</string>
|
||||||
|
<string name="settings_language">语言</string>
|
||||||
|
<string name="settings_language_description">选择应用语言</string>
|
||||||
|
<string name="settings_system_default">跟随系统</string>
|
||||||
|
<string name="settings_contact">联系与反馈</string>
|
||||||
|
<string name="settings_contact_description">向我们发送反馈</string>
|
||||||
|
<string name="settings_logout">退出登录</string>
|
||||||
|
<string name="settings_logout_description">退出您的账户</string>
|
||||||
|
<string name="settings_version">版本 %s</string>
|
||||||
|
<string name="settings_select_language">选择语言</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask 反馈</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -8,4 +8,5 @@
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
<color name="fastmail_blue">#0066CC</color>
|
<color name="fastmail_blue">#0066CC</color>
|
||||||
|
<color name="splash_background">#0066CC</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
17
app/src/main/res/values/font_certs.xml
Normal file
17
app/src/main/res/values/font_certs.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<array name="com_google_android_gms_fonts_certs">
|
||||||
|
<item>@array/com_google_android_gms_fonts_certs_dev</item>
|
||||||
|
<item>@array/com_google_android_gms_fonts_certs_prod</item>
|
||||||
|
</array>
|
||||||
|
<string-array name="com_google_android_gms_fonts_certs_dev">
|
||||||
|
<item>
|
||||||
|
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VBER8YLKLpi2SQnkhJNPwMIHJBgNVHSMEgcEwgb6AFI0cxb6VBER8YLKLpi2SQnkhJNPwoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/sgPkgfRIwbgsGa/ENYW66HZFSfRQMzY93AqcxLQpXYkzswLrXnQwAvevk+5L2c4GYzd+auiV8gMZ+Q==
|
||||||
|
</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="com_google_android_gms_fonts_certs_prod">
|
||||||
|
<item>
|
||||||
|
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14teleKRcpFwMXQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZIIJAMLgh0ZkSjCNMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABbC5SvHdSv+BYkQJj4ZsJb1EJey+U1Y8/Lsu4Ao+HEENFLyB0w3FRqw5fsHsaOydl89Wv6R/1LDgRvTg8iBT2dDm6NVN8nkTvZJEpEhHI6Y0x0fJM/RwgMEZ3bS4B1R4EFmEeZm42I8FHX2J07r+qfuxW+cOCLhFOp0VLQ+U2lYzHhJ2J5UNnVrKNU/4u8J3TnmmJbTpKJmI5yEpGMCjZ+VsZDh3H5JXRk+BjE2e+T/OL1JFJpW1DQNI+VDLbmgHKhE8TN+HQGM7Hy5P9FxE2OGJ1f6s0k8PZXX2T2vPj9YJyqCIINCZlqU7aIyWM3h5z2qnWRL6vZE6mQ3ZIpWQ=
|
||||||
|
</item>
|
||||||
|
</string-array>
|
||||||
|
</resources>
|
||||||
|
|
@ -1,4 +1,121 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<!-- App Name -->
|
||||||
<string name="app_name">FastMask</string>
|
<string name="app_name">FastMask</string>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<string name="login_subtitle">Fastmail Masked Email Manager</string>
|
||||||
|
<string name="login_api_token_label">API Token</string>
|
||||||
|
<string name="login_api_token_placeholder">Enter your Fastmail API token</string>
|
||||||
|
<string name="login_button">Login</string>
|
||||||
|
<string name="login_show_token">Show token</string>
|
||||||
|
<string name="login_hide_token">Hide token</string>
|
||||||
|
<string name="login_instructions_title">How to get your API token:</string>
|
||||||
|
<string name="login_instructions_step1">1. Log in to Fastmail web app</string>
|
||||||
|
<string name="login_instructions_step2">2. Go to Settings > Privacy & Security</string>
|
||||||
|
<string name="login_instructions_step3">3. Click on Integrations > API tokens</string>
|
||||||
|
<string name="login_instructions_step4">4. Create a new token with \"Masked Email\" scope</string>
|
||||||
|
<string name="login_instructions_step5">5. Copy the token and paste it above</string>
|
||||||
|
<string name="login_instructions_full">1. Log in to Fastmail web app\n2. Go to Settings > Privacy & Security\n3. Click on Integrations > API tokens\n4. Create a new token with \"Masked Email\" scope\n5. Copy the token and paste it above</string>
|
||||||
|
|
||||||
|
<!-- Email List Screen -->
|
||||||
|
<string name="email_list_title">Masked Emails</string>
|
||||||
|
<string name="email_list_search_placeholder">Search emails…</string>
|
||||||
|
<string name="email_list_clear_search">Clear search</string>
|
||||||
|
<string name="email_list_filter_emails">Filter emails</string>
|
||||||
|
<string name="email_list_create_fab">Create</string>
|
||||||
|
<string name="email_list_create_description">Create new masked email</string>
|
||||||
|
<string name="email_list_empty">No masked emails found</string>
|
||||||
|
<string name="email_list_logout">Logout</string>
|
||||||
|
<string name="email_list_settings">Settings</string>
|
||||||
|
<string name="filter_all">All</string>
|
||||||
|
<string name="filter_enabled">Enabled</string>
|
||||||
|
<string name="filter_disabled">Disabled</string>
|
||||||
|
<string name="filter_deleted">Deleted</string>
|
||||||
|
|
||||||
|
<!-- Create Email Screen -->
|
||||||
|
<string name="create_email_title">Create Masked Email</string>
|
||||||
|
<string name="create_email_prefix_label">Email Prefix (optional)</string>
|
||||||
|
<string name="create_email_prefix_placeholder">e.g., mysite_shopping</string>
|
||||||
|
<string name="create_email_prefix_hint">Max 64 chars: lowercase letters, numbers, underscores</string>
|
||||||
|
<string name="create_email_domain_label">Associated Domain (optional)</string>
|
||||||
|
<string name="create_email_domain_placeholder">e.g., example.com</string>
|
||||||
|
<string name="create_email_description_label">Description (optional)</string>
|
||||||
|
<string name="create_email_description_placeholder">e.g., Shopping account</string>
|
||||||
|
<string name="create_email_url_label">URL (optional)</string>
|
||||||
|
<string name="create_email_url_placeholder">e.g., https://example.com</string>
|
||||||
|
<string name="create_email_initial_state">Initial State</string>
|
||||||
|
<string name="create_email_button">Create Masked Email</string>
|
||||||
|
<string name="create_email_created">Created: %s</string>
|
||||||
|
<string name="create_email_copy_action">Copy</string>
|
||||||
|
<string name="navigate_back">Navigate back</string>
|
||||||
|
|
||||||
|
<!-- Email Detail Screen -->
|
||||||
|
<string name="email_detail_title">Email Details</string>
|
||||||
|
<string name="email_detail_copy_email">Copy email address</string>
|
||||||
|
<string name="email_detail_delete">Delete email</string>
|
||||||
|
<string name="email_detail_status">Status:</string>
|
||||||
|
<string name="email_detail_created_by">Created by: %s</string>
|
||||||
|
<string name="email_detail_created">Created: %s</string>
|
||||||
|
<string name="email_detail_last_message">Last message: %s</string>
|
||||||
|
<string name="email_detail_enable">Enable</string>
|
||||||
|
<string name="email_detail_disable">Disable</string>
|
||||||
|
<string name="email_detail_edit_title">Edit Details</string>
|
||||||
|
<string name="email_detail_description_label">Description</string>
|
||||||
|
<string name="email_detail_domain_label">Associated Domain</string>
|
||||||
|
<string name="email_detail_url_label">URL</string>
|
||||||
|
<string name="email_detail_save">Save Changes</string>
|
||||||
|
<string name="email_detail_updated">Updated successfully</string>
|
||||||
|
<string name="email_detail_deleted">Deleted successfully</string>
|
||||||
|
<string name="email_detail_copied">Copied to clipboard</string>
|
||||||
|
<string name="email_detail_delete_dialog_title">Delete Masked Email</string>
|
||||||
|
<string name="email_detail_delete_dialog_message">Are you sure you want to delete this masked email? This action cannot be undone.</string>
|
||||||
|
<string name="email_detail_delete_confirm">Delete</string>
|
||||||
|
<string name="email_detail_delete_cancel">Cancel</string>
|
||||||
|
|
||||||
|
<!-- Email States -->
|
||||||
|
<string name="state_enabled">Enabled</string>
|
||||||
|
<string name="state_disabled">Disabled</string>
|
||||||
|
<string name="state_deleted">Deleted</string>
|
||||||
|
<string name="state_pending">Pending</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_load_emails">Failed to load emails</string>
|
||||||
|
<string name="error_retry">Retry</string>
|
||||||
|
<string name="error_generic">An error occurred</string>
|
||||||
|
|
||||||
|
<!-- Settings Screen -->
|
||||||
|
<string name="settings_title">Settings</string>
|
||||||
|
<string name="settings_language">Language</string>
|
||||||
|
<string name="settings_language_description">Select app language</string>
|
||||||
|
<string name="settings_system_default">System Default</string>
|
||||||
|
<string name="settings_contact">Contact & Feedback</string>
|
||||||
|
<string name="settings_contact_description">Send us your feedback</string>
|
||||||
|
<string name="settings_logout">Logout</string>
|
||||||
|
<string name="settings_logout_description">Sign out of your account</string>
|
||||||
|
<string name="settings_version">Version %s</string>
|
||||||
|
<string name="settings_select_language">Select Language</string>
|
||||||
|
<string name="settings_feedback_subject">FastMask Feedback</string>
|
||||||
|
|
||||||
|
<!-- Language Names (Native) -->
|
||||||
|
<string name="language_en">English</string>
|
||||||
|
<string name="language_zh">中文</string>
|
||||||
|
<string name="language_es">Español</string>
|
||||||
|
<string name="language_hi">हिन्दी</string>
|
||||||
|
<string name="language_ar">العربية</string>
|
||||||
|
<string name="language_pt">Português</string>
|
||||||
|
<string name="language_bn">বাংলা</string>
|
||||||
|
<string name="language_ru">Русский</string>
|
||||||
|
<string name="language_ja">日本語</string>
|
||||||
|
<string name="language_fr">Français</string>
|
||||||
|
<string name="language_de">Deutsch</string>
|
||||||
|
<string name="language_ko">한국어</string>
|
||||||
|
<string name="language_it">Italiano</string>
|
||||||
|
<string name="language_tr">Türkçe</string>
|
||||||
|
<string name="language_vi">Tiếng Việt</string>
|
||||||
|
<string name="language_pl">Polski</string>
|
||||||
|
<string name="language_uk">Українська</string>
|
||||||
|
<string name="language_nl">Nederlands</string>
|
||||||
|
<string name="language_th">ไทย</string>
|
||||||
|
<string name="language_id">Bahasa Indonesia</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.FastMask" parent="android:Theme.Material.Light.NoActionBar">
|
<style name="Theme.FastMask" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.FastMask.Splash" parent="Theme.SplashScreen">
|
||||||
|
<item name="windowSplashScreenBackground">@color/splash_background</item>
|
||||||
|
<item name="windowSplashScreenAnimatedIcon">@drawable/ic_launcher_foreground</item>
|
||||||
|
<item name="postSplashScreenTheme">@style/Theme.FastMask</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
23
app/src/main/res/xml/locales_config.xml
Normal file
23
app/src/main/res/xml/locales_config.xml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<locale android:name="en" />
|
||||||
|
<locale android:name="zh-Hans" />
|
||||||
|
<locale android:name="es" />
|
||||||
|
<locale android:name="hi" />
|
||||||
|
<locale android:name="ar" />
|
||||||
|
<locale android:name="pt" />
|
||||||
|
<locale android:name="bn" />
|
||||||
|
<locale android:name="ru" />
|
||||||
|
<locale android:name="ja" />
|
||||||
|
<locale android:name="fr" />
|
||||||
|
<locale android:name="de" />
|
||||||
|
<locale android:name="ko" />
|
||||||
|
<locale android:name="it" />
|
||||||
|
<locale android:name="tr" />
|
||||||
|
<locale android:name="vi" />
|
||||||
|
<locale android:name="pl" />
|
||||||
|
<locale android:name="uk" />
|
||||||
|
<locale android:name="nl" />
|
||||||
|
<locale android:name="th" />
|
||||||
|
<locale android:name="id" />
|
||||||
|
</locale-config>
|
||||||
|
|
@ -1,27 +1,48 @@
|
||||||
---
|
---
|
||||||
layout: default
|
layout: default
|
||||||
title: FastMask - Fastmail Masked Email Manager for Android
|
title: FastMask - Fastmail Masked Email Manager for Android
|
||||||
|
description: Native Android app for managing Fastmail masked emails. Create, view, edit, and delete masked email addresses with Material 3 design.
|
||||||
---
|
---
|
||||||
|
|
||||||
# FastMask
|
# FastMask
|
||||||
|
|
||||||
A native Android app for managing Fastmail masked emails.
|
**Native Android app for managing Fastmail masked emails**
|
||||||
|
|
||||||
Create, view, edit, and manage your masked email addresses directly from your Android device.
|
[](https://github.com/pawelorzech/FastMask/releases/latest)
|
||||||
|
[](https://github.com/pawelorzech/FastMask/blob/main/LICENSE)
|
||||||
|
[](https://developer.android.com/about/versions/oreo)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
**[Download Latest APK](https://github.com/pawelorzech/FastMask/releases/latest)**
|
<a href="https://github.com/pawelorzech/FastMask/releases/latest" style="display: inline-block; padding: 12px 24px; background-color: #6750A4; color: white; text-decoration: none; border-radius: 8px; font-weight: bold;">Download Latest APK</a>
|
||||||
|
|
||||||
|
Or view all releases on [GitHub](https://github.com/pawelorzech/FastMask/releases)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What is FastMask?
|
||||||
|
|
||||||
|
FastMask lets you manage your [Fastmail](https://www.fastmail.com) masked email addresses directly from your Android phone.
|
||||||
|
|
||||||
|
**Masked emails** are disposable addresses that forward to your real inbox. They help you:
|
||||||
|
- Protect your real email from spam
|
||||||
|
- Track which services share your email
|
||||||
|
- Easily disable addresses if they get compromised
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **View Masked Emails** - Browse all your Fastmail masked email addresses
|
| Feature | Description |
|
||||||
- **Create New Masks** - Generate new masked email addresses instantly
|
|---------|-------------|
|
||||||
- **Enable/Disable** - Toggle masked emails on or off
|
| **View All Masks** | Browse your masked emails in a clean, searchable list |
|
||||||
- **Edit Details** - Update description, domain, and URL
|
| **Create New** | Generate new masked addresses with custom descriptions |
|
||||||
- **Copy to Clipboard** - Quick one-tap copy
|
| **Enable/Disable** | Toggle masks on or off without deleting them |
|
||||||
- **Search** - Filter your masked emails
|
| **Edit Details** | Update description, domain, and URL associations |
|
||||||
- **Material You** - Modern Material 3 design
|
| **Quick Copy** | One-tap copy to clipboard |
|
||||||
|
| **Delete** | Remove masks you no longer need |
|
||||||
|
| **Search & Filter** | Find specific masks instantly |
|
||||||
|
| **Material You** | Dynamic theming that adapts to your wallpaper |
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
@ -30,21 +51,59 @@ Create, view, edit, and manage your masked email addresses directly from your An
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
1. Download the APK from [Releases](https://github.com/pawelorzech/FastMask/releases)
|
### 1. Install the App
|
||||||
2. Install the app on your Android device
|
|
||||||
3. Create a Fastmail API token at [Fastmail Settings](https://www.fastmail.com/settings/security/tokens)
|
|
||||||
4. Enter your token in FastMask to start managing your masked emails
|
|
||||||
|
|
||||||
## Privacy
|
Download the APK from [Releases](https://github.com/pawelorzech/FastMask/releases) and install it.
|
||||||
|
|
||||||
- Your API token is stored securely using Android's EncryptedSharedPreferences
|
### 2. Create a Fastmail API Token
|
||||||
- Direct communication with Fastmail's API - no third-party servers
|
|
||||||
- No analytics or tracking
|
|
||||||
|
|
||||||
## Source Code
|
1. Log in to [Fastmail](https://www.fastmail.com)
|
||||||
|
2. Go to **Settings** → **Privacy & Security** → **Integrations** → **API tokens**
|
||||||
|
3. Click **New API token**
|
||||||
|
4. Name it "FastMask"
|
||||||
|
5. Select scope: **Masked Email** (read/write)
|
||||||
|
6. Copy the token
|
||||||
|
|
||||||
FastMask is open source. View the code on [GitHub](https://github.com/pawelorzech/FastMask).
|
### 3. Log In
|
||||||
|
|
||||||
|
Open FastMask, paste your token, and tap "Log in".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Privacy & Security
|
||||||
|
|
||||||
|
- **Secure Storage**: Your API token is encrypted using Android's EncryptedSharedPreferences
|
||||||
|
- **Direct Connection**: The app talks directly to Fastmail - no middleman servers
|
||||||
|
- **No Tracking**: Zero analytics or data collection
|
||||||
|
- **Open Source**: [Full source code](https://github.com/pawelorzech/FastMask) available for review
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Kotlin** - 100% Kotlin codebase
|
||||||
|
- **Jetpack Compose** - Modern declarative UI
|
||||||
|
- **Material 3** - Latest Material Design with dynamic theming
|
||||||
|
- **JMAP** - Fastmail's native API protocol
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
FastMask is open source and welcomes contributions!
|
||||||
|
|
||||||
|
- [View Source Code](https://github.com/pawelorzech/FastMask)
|
||||||
|
- [Report a Bug](https://github.com/pawelorzech/FastMask/issues/new?template=bug_report.md)
|
||||||
|
- [Request a Feature](https://github.com/pawelorzech/FastMask/issues/new?template=feature_request.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
FastMask is released under the [MIT License](https://github.com/pawelorzech/FastMask/blob/main/LICENSE).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p style="text-align: center; color: #666; margin-top: 40px;">
|
||||||
Made with Kotlin and Jetpack Compose
|
Made with Kotlin and Jetpack Compose
|
||||||
|
</p>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue