Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ project/localscripts
jacoco.exec
.env
fastlane/report.xml

# API keys
project/app/gradle.properties
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,258 changes: 1,258 additions & 0 deletions .idea/caches/deviceStreaming.xml

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions .idea/markdown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

329 changes: 329 additions & 0 deletions project/.claude/compose-migration-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
# OwnTracks Compose Migration Plan

## Overview
Migrating OwnTracks Android app from Activities/Fragments with XML layouts to Jetpack Compose with Navigation Compose. The drawer navigation has been replaced with bottom navigation.

## Current Status

### Completed Infrastructure
- [x] Compose dependencies added to `libs.versions.toml`
- [x] Compose enabled in `app/build.gradle.kts`
- [x] Theme files created (`ui/theme/Color.kt`, `Theme.kt`, `Type.kt`)
- [x] Navigation infrastructure (`ui/navigation/Destinations.kt`, `OwnTracksNavHost.kt`)
- [x] ~~Compose drawer component (`ui/navigation/AppDrawer.kt`)~~ (deleted - was deprecated)
- [x] Bottom navigation component (`ui/navigation/BottomNavBar.kt`)
- [x] Bottom nav menu (`res/menu/bottom_nav_menu.xml`)

### Bottom Navigation Migration (COMPLETED)
Replaced drawer navigation with 4-tab bottom navigation across all main screens:

| Tab | Icon | Screen |
|-----|------|--------|
| Map | map | Main map view |
| Contacts | people | Contact list |
| Waypoints | location_on | Regions/geofences |
| Preferences | settings | Settings + Status + About |

**Changes made:**
- Created `BottomNavBar.kt` Compose component
- Created `bottom_nav_menu.xml` menu resource
- Updated `MapActivity` - removed DrawerLayout, added BottomNavigationView
- Updated `ContactsActivity/ContactsScreen` - replaced drawer with BottomNavBar
- Updated `WaypointsActivity/WaypointsScreen` - replaced drawer with BottomNavBar
- Updated `PreferencesActivity` - removed drawer, added BottomNavigationView
- Updated `StatusActivity/StatusScreen` - removed drawer, now uses back navigation (accessed from Preferences)
- Status, About, Exit are now handled directly in PreferencesScreen.kt Compose UI

### Migrated Screens

#### 1. Status Screen (Now accessed via Preferences)
- **Files**: `StatusActivity.kt`, `StatusScreen.kt`
- **Deleted**: `ui_status.xml`
- **Features**: Status items, battery optimization dialog, location permissions dialog, view logs button
- **Navigation**: Back button (accessed from Preferences > Status)

#### 2. Contacts Screen
- **Files**: `ContactsActivity.kt`, `ContactsScreen.kt`
- **Deleted**: `ui_contacts.xml`
- **Features**: LazyColumn with contacts, contact avatar loading via `ContactImageBindingAdapter`, relative timestamps, geocoded locations
- **Navigation**: Bottom navigation bar

#### 3. Waypoints Screen
- **Files**: `WaypointsActivity.kt`, `WaypointsScreen.kt`
- **Deleted**: `ui_waypoints.xml`
- **Features**: LazyColumn with waypoints, overflow menu (import/export), add button, geofence transition status
- **Navigation**: Bottom navigation bar

#### 4. About Screen (Accessed via Preferences)
- **Files**: `AboutActivity.kt`, `AboutScreen.kt`, `LicensesScreen.kt`
- **Deleted**: `AboutFragment.kt`, `LicenseFragment.kt`, `about.xml`, `preferences_licenses.xml`
- **Features**:
- Version info with changelog link
- Documentation, License, Source Code links
- Libraries/Licenses screen (internal navigation)
- Translations with plural string support
- Feedback section (Issues, Mastodon)
- **Navigation**: Back button (accessed from Preferences > About)

### Migrated Main Screens

#### 5. Preferences Screen (MIGRATED TO COMPOSE - CLEANUP COMPLETED)
- **Files**: `PreferencesActivity.kt`, `PreferencesScreen.kt`
- **New Files**:
- `PreferenceItems.kt` - Reusable preference composables (SwitchPreference, EditTextPreference, ListPreference, etc.)
- `ConnectionPreferencesContent.kt` - Connection/server settings with reconnect action
- `MapPreferencesContent.kt` - Map display settings
- `ReportingPreferencesContent.kt` - Location reporting settings
- `NotificationPreferencesContent.kt` - Notification settings
- `AdvancedPreferencesContent.kt` - Advanced settings (services, locator, encryption, misc)
- `ExperimentalPreferencesContent.kt` - Experimental features placeholder
- **Deleted Legacy Files**:
- Kotlin: `PreferencesFragment.kt`, `AbstractPreferenceFragment.kt`, `ConnectionFragment.kt`, `MapFragment.kt`, `ReportingFragment.kt`, `NotificationFragment.kt`, `AdvancedFragment.kt`, `ExperimentalFragment.kt`, `ValidatingEditTextPreferenceDialogFragmentCompat.kt`, `PreferencesMenuProvider.kt`
- XML: `preferences_root.xml`, `preferences_connection.xml`, `preferences_map.xml`, `preferences_reporting.xml`, `preferences_notification.xml`, `preferences_advanced.xml`, `preferences_experimental.xml`, `menu/preferences_connection.xml`
- **Layout**: Uses `setContent` with Compose, no longer uses XML layouts or fragments
- **Navigation**: Bottom navigation bar via `BottomNavBar` composable
- **Contains**: Internal navigation to sub-screens (Connection, Map, Reporting, Notification, Advanced, Experimental)
- **Links to**: Status, About, Editor screens via Activity intents

#### 6. Map Screen (Partial Compose Migration)
- **Current**: `MapActivity.kt`, `MapFragment.kt` (GMS/OSS variants), `ui_map.xml`
- **Layout**: CoordinatorLayout with BottomNavigationView + ComposeView overlay for FABs and bottom sheet
- **Navigation**: Bottom navigation bar
- **Features**: Map fragment, Compose FABs, Compose ModalBottomSheet for contact details
- **Bottom Sheet Migration (Phase 2 COMPLETED)**:
- Created `ContactBottomSheet.kt` - Compose ModalBottomSheet for contact details
- Displays contact info: avatar, name, timestamp, geocoded location
- Contact details grid: accuracy, altitude, battery, speed, distance, bearing
- Action buttons: Request Location, Navigate, Clear, Share
- Deleted `ui_contactsheet_parameter.xml` and `AutoResizingTextViewWithListener.kt`
- **FABs Migration (Phase 3 COMPLETED)**:
- Created `MapFabs.kt` - Compose component with MapLayersFab and MyLocationFab
- MyLocationFab shows dynamic icon based on `MyLocationStatus` (disabled, available, following)
- Removed FABs from `ui_map.xml`
- Removed `@BindingAdapter("locationIcon")` from MapActivity companion object

### Secondary Screens (Back Navigation)

##### 7.1 LogViewerActivity (Migrated)
- **Files**: `LogViewerActivity.kt`, `LogViewerScreen.kt`
- **Deleted**: `LogEntryAdapter.kt`, `LogPalette.kt`, `ui_preferences_logs.xml`, `log_viewer_entry.xml`
- **Features**:
- LazyColumn with log entries (horizontally scrollable)
- Color-coded log levels (debug, info, warning, error)
- Auto-scroll to bottom when new logs arrive
- Overflow menu (toggle debug logs, clear)
- FAB for sharing/exporting logs
- Back button navigation

##### 7.2 WaypointActivity (Migrated)
- **Files**: `WaypointActivity.kt`, `WaypointScreen.kt`
- **Deleted**: `ui_waypoint.xml`
- **Features**:
- Form with OutlinedTextField for description, latitude, longitude, radius
- Input validation with error states
- Save/Delete action buttons in TopAppBar
- Delete confirmation dialog
- Back button navigation
- **Note**: Moved `@BindingAdapter` functions for `relativeTimeSpanString` to `support/BindingAdapters.kt` (still used by ui_row_contact.xml and ui_row_waypoint.xml)

##### 7.3 WelcomeActivity (Migrated)
- **Files**: `BaseWelcomeActivity.kt`, `WelcomeScreen.kt`, `PlayServicesPage.kt` (gms)
- **Deleted**:
- Layouts: `ui_welcome.xml`, `ui_welcome_intro.xml`, `ui_welcome_connection_setup.xml`, `ui_welcome_location_permission.xml`, `ui_welcome_notification_permission.xml`, `ui_welcome_finish.xml`, `ui_welcome_play.xml`
- Kotlin: `WelcomeAdapter.kt`, `WelcomeViewModel.kt`, `WelcomeFragment.kt`, `IntroFragment.kt`, `ConnectionSetupFragment.kt`, `LocationPermissionFragment.kt`, `NotificationPermissionFragment.kt`, `FinishFragment.kt`, `PlayFragment.kt`, `PlayFragmentViewModel.kt`
- **Features**:
- HorizontalPager for swipeable onboarding pages
- Page indicator dots
- Permission requests with `rememberLauncherForActivityResult`
- GMS/OSS variants with different page lists
- Play Services check page (GMS only)
- Dynamic page list based on API level (NotificationPermission for Android 13+)

##### 7.4 EditorActivity (Migrated)
- **Files**: `EditorActivity.kt`, `EditorScreen.kt`
- **Deleted**: `ui_preferences_editor.xml`, `ui_preferences_editor_dialog.xml`
- **Features**:
- Display effective configuration as JSON
- Overflow menu (export, import file, edit single value)
- Snackbar for feedback messages
- Preference editor dialog with autocomplete for keys (ExposedDropdownMenuBox)
- Export configuration to file picker
- Back button navigation

##### 7.5 LoadActivity (Migrated)
- **Files**: `LoadActivity.kt`, `LoadScreen.kt`
- **Deleted**: `ui_preferences_load.xml`
- **Features**:
- Loading state with CircularProgressIndicator
- Success state showing imported configuration JSON
- Failed state showing error message
- Close/Save action buttons based on import status
- Handles ACTION_VIEW intents, content URIs, owntracks:// scheme
- File picker for manual import

## Established Patterns

### Activity Structure with Bottom Nav (Compose)
```kotlin
@AndroidEntryPoint
class SomeActivity : AppCompatActivity() {
private val viewModel: SomeViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)

setContent {
OwnTracksTheme {
SomeScreen(
viewModel = viewModel,
onNavigate = { destination ->
navigateToDestination(destination)
},
// ... other callbacks
)
}
}
}

private fun navigateToDestination(destination: Destination) {
val activityClass = destination.toActivityClass() ?: return
if (this.javaClass != activityClass) {
startActivity(Intent(this, activityClass))
}
}
}
```

### Screen Composable Structure with Bottom Nav
```kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SomeScreen(
// State from ViewModel
someData: List<Item>,
// Callbacks
onNavigate: (Destination) -> Unit,
onItemClick: (Item) -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.title)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary
)
)
},
bottomBar = {
BottomNavBar(
currentDestination = Destination.SomeScreen,
onNavigate = onNavigate
)
},
modifier = modifier
) { paddingValues ->
// Content
}
}
```

### Secondary Screen with Back Navigation
```kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SecondaryScreen(
onBackClick: () -> Unit,
modifier: Modifier = Modifier
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.title)) },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
}
},
colors = TopAppBarDefaults.topAppBarColors(...)
)
},
modifier = modifier
) { paddingValues ->
// Content
}
}
```

### State Collection
- StateFlow: `val state by viewModel.stateFlow.collectAsStateWithLifecycle()`
- LiveData: `val state by viewModel.liveData.observeAsState(initial = defaultValue)`
- Flow events: Use `LaunchedEffect` to collect

### Hybrid View + Compose (MapActivity Pattern)
For screens that can't fully migrate to Compose (e.g., MapActivity with MapFragment):
```kotlin
// In XML layout, add a ComposeView for Compose overlay:
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeBottomSheet"
android:layout_width="match_parent"
android:layout_height="match_parent" />

// In Activity, set Compose content on the ComposeView:
binding.composeBottomSheet.setContent {
OwnTracksTheme {
val state by viewModel.someState.observeAsState()
if (showBottomSheet) {
ContactBottomSheet(
contact = contact,
onDismiss = { /* handle dismiss */ },
// ... other callbacks
)
}
}
}
```

## Future Work

### Potential Improvements
1. **Single Activity Architecture**: Consolidate all screens into a single MainActivity with NavHost
2. **Map Screen Compose Migration**: Phase 4 as outlined below (Phases 1-3 completed)
3. ~~**Preferences Screen Compose Migration**: Build custom Compose preferences UI~~ (COMPLETED)

### Map Screen Migration Phases (Optional)
1. ~~**Phase 1 (Completed)**: Added bottom navigation, removed drawer~~
2. ~~**Phase 2 (Completed)**: Migrated bottom sheet content to Compose ModalBottomSheet~~
3. ~~**Phase 3 (Completed)**: Migrated FABs to Compose FloatingActionButton~~
4. **Phase 4 (Full migration)**: Convert to single-Activity with NavHost

## Build Command
```bash
./gradlew assembleGmsDebug --no-daemon -Dorg.gradle.java.home="C:\Program Files\Java\jdk-22"
```

## Dependencies Added
```toml
# In libs.versions.toml
compose-bom = "2024.12.01"
compose-compiler = "1.5.15"
navigation-compose = "2.8.5"
lifecycle-runtime-compose = "2.8.7"

# Libraries
compose-bom, compose-ui, compose-ui-graphics, compose-ui-tooling,
compose-ui-tooling-preview, compose-material3, compose-material-icons-extended,
compose-foundation, compose-runtime-livedata, activity-compose,
navigation-compose, lifecycle-runtime-compose
```

## Notes
- Kotlin version is 1.9.25, requires Compose Compiler 1.5.x (not 2.x)
- DataBinding is still enabled for MapActivity (uses Compose overlay pattern)
- ViewModels remain unchanged - they work with both View system and Compose
- Hilt injection unchanged - @AndroidEntryPoint still works
- AppDrawer.kt deleted - bottom navigation is now used instead
- PreferencesActivity fully migrated to Compose - all legacy Fragment files and XML resources deleted
- SharedPreferencesStore.kt updated to remove references to deleted ConnectionFragment
Loading