Merge pull request #4 from mattintech/feature/depInj

adding shared prefs to save the users name
This commit is contained in:
2025-07-03 21:32:03 -04:00
committed by GitHub
4 changed files with 201 additions and 27 deletions

151
TODO.md
View File

@@ -9,11 +9,27 @@
- [x] Implement proper state management with LiveData/StateFlow - [x] Implement proper state management with LiveData/StateFlow
- [x] Add ViewModelFactory if needed - [x] Add ViewModelFactory if needed
### 1.2 Dependency Injection with Hilt ### 1.2 Dependency Injection with Hilt
- [ ] Add Hilt dependencies - [x] Add Hilt dependencies
- [ ] Set up Hilt modules for WifiAwareManager - [x] Set up Hilt modules for WifiAwareManager
- [ ] Convert singletons to proper DI - [x] Convert singletons to proper DI
- [ ] Inject ViewModels using Hilt - [x] Inject ViewModels using Hilt
### 1.3 Room Database Setup
- [ ] Add Room dependencies
- [ ] Create Message and User entities
- [ ] Implement DAOs for data access
- [ ] Create database migrations
- [ ] Store messages in Room database
- [ ] Load message history on app restart
- [ ] Implement message sync logic
### 1.4 Coroutines & Flow Optimization
- [ ] Convert callbacks to coroutines
- [ ] Use Flow for reactive data streams
- [ ] Implement proper scope management
- [ ] Replace GlobalScope with proper lifecycle scopes
- [ ] Add proper error handling with coroutines
## Phase 2: Core UX Improvements ## Phase 2: Core UX Improvements
@@ -29,19 +45,50 @@
- [ ] Handle user join/leave events - [ ] Handle user join/leave events
- [ ] Show user count in chat header - [ ] Show user count in chat header
## Phase 3: Data Persistence ### 2.3 Enhanced Messaging Features
- [ ] Message status indicators (sent/delivered/read)
- [ ] User presence indicators (online/offline/typing)
- [ ] Message timestamps with proper formatting
- [ ] Offline message queue
- [ ] Message retry mechanism
- [ ] Long press message actions (copy, delete)
### 3.1 Room Database Setup ### 2.4 File & Media Sharing
- [ ] Add Room dependencies - [ ] Image sharing support
- [ ] Create Message and User entities - [ ] File transfer capability
- [ ] Implement DAOs for data access - [ ] Image preview in chat
- [ ] Create database migrations - [ ] Progress indicators for transfers
- [ ] File size limits and validation
### 3.2 Message Persistence ## Phase 3: UI/UX Improvements
- [ ] Store messages in Room database
- [ ] Load message history on app restart ### 3.1 Material 3 Design Update
- [ ] Implement message sync logic - [ ] Migrate to Material 3 components
- [ ] Add message timestamps - [ ] Implement dynamic color theming
- [ ] Update typography and spacing
- [ ] Add proper elevation and shadows
- [ ] Implement Material You design principles
### 3.2 Dark Theme & Theming
- [ ] Implement dark theme
- [ ] Add theme toggle in settings
- [ ] System theme detection
- [ ] Custom color schemes
- [ ] Persist theme preference
### 3.3 Animations & Polish
- [ ] Message send/receive animations
- [ ] Screen transition animations
- [ ] Loading state animations
- [ ] Smooth scrolling improvements
- [ ] Haptic feedback
### 3.4 Better Error Handling UI
- [ ] User-friendly error messages
- [ ] Retry mechanisms with UI feedback
- [ ] Connection lost/restored snackbars
- [ ] Empty states for no messages/users
- [ ] Inline error states
## Phase 4: Reliability Improvements ## Phase 4: Reliability Improvements
@@ -56,26 +103,78 @@
- [ ] Handle app lifecycle properly - [ ] Handle app lifecycle properly
- [ ] Save and restore connection state - [ ] Save and restore connection state
## Phase 5: Advanced Features ## Phase 5: Security & Privacy
### 5.1 Background Service ### 5.1 Message Encryption
- [ ] End-to-end encryption implementation
- [ ] Key exchange protocol
- [ ] Message integrity verification
- [ ] Secure key storage
- [ ] Forward secrecy
### 5.2 Privacy Features
- [ ] Optional username anonymization
- [ ] Message auto-deletion
- [ ] Block/unblock users
- [ ] Private rooms with passwords
- [ ] Data export/import
## Phase 6: Advanced Features
### 6.1 Background Service
- [ ] Create foreground service for persistent connection - [ ] Create foreground service for persistent connection
- [ ] Handle Doze mode and battery optimization - [ ] Handle Doze mode and battery optimization
- [ ] Add notification for active chat - [ ] Add notification for active chat
- [ ] Implement proper service lifecycle - [ ] Implement proper service lifecycle
- [ ] Wake lock management
### 5.2 Additional Features ### 6.2 Settings & Preferences
- [ ] Message delivery status - [ ] Create settings screen
- [ ] Typing indicators - [ ] Notification preferences
- [ ] File/image sharing support - [ ] Sound/vibration settings
- [ ] Message encryption improvements - [ ] Auto-reconnect toggle
- [ ] Message history limits
## Phase 7: Testing & Quality
### 7.1 Unit Testing
- [ ] Test ViewModels
- [ ] Test Repository logic
- [ ] Test data transformations
- [ ] Test error scenarios
- [ ] Mock dependencies with Hilt testing
### 7.2 Integration Testing
- [ ] Test database operations
- [ ] Test network layer
- [ ] Test complete user flows
- [ ] Test state persistence
### 7.3 UI Testing
- [ ] Espresso tests for main flows
- [ ] Test navigation
- [ ] Test user interactions
- [ ] Screenshot testing
- [ ] Accessibility testing
## Current Status ## Current Status
- ✅ Phase 1.1 - MVVM Architecture - COMPLETED - ✅ Phase 1.1 - MVVM Architecture - COMPLETED
- ✅ Phase 1.2 - Dependency Injection with Hilt - COMPLETED
- ✅ Phase 2.1 - Connection Status Management - COMPLETED - ✅ Phase 2.1 - Connection Status Management - COMPLETED
- 🚀 Next: Phase 1.2 (Dependency Injection) or Phase 3 (Data Persistence) - 🚀 Next Priority Options:
- Phase 1.3 - Room Database (Foundation for persistence)
- Phase 2.2 - User List Feature (Core UX)
- Phase 2.3 - Enhanced Messaging (Better UX)
- Phase 3.1 - Material 3 Update (Modern UI)
## Completed Work Summary ## Completed Work Summary
1. **MVVM Architecture**: ViewModels, Repository pattern, proper separation of concerns 1. **MVVM Architecture**: ViewModels, Repository pattern, proper separation of concerns
2. **Connection Status**: Visual indicator with real-time updates, activity-based detection 2. **Dependency Injection**: Hilt integration with proper scoping and lifecycle management
3. **Sleep/Wake Handling**: Auto-recovery when messages resume after device sleep 3. **Connection Status**: Visual indicator with real-time updates, activity-based detection
4. **Sleep/Wake Handling**: Auto-recovery when messages resume after device sleep
## Development Notes
- Architecture foundation (Phase 1) should be completed before moving to advanced features
- UI/UX improvements (Phase 3) can be done in parallel with feature development
- Testing (Phase 7) should be implemented incrementally as features are added
- Security features (Phase 5) are important for production readiness

View File

@@ -1,6 +1,8 @@
package com.mattintech.lchat.ui package com.mattintech.lchat.ui
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@@ -48,6 +50,18 @@ class LobbyFragment : Fragment() {
} }
private fun setupUI() { private fun setupUI() {
binding.nameInput.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
s?.toString()?.trim()?.let { name ->
viewModel.saveUserName(name)
}
}
})
binding.modeRadioGroup.setOnCheckedChangeListener { _, checkedId -> binding.modeRadioGroup.setOnCheckedChangeListener { _, checkedId ->
when (checkedId) { when (checkedId) {
R.id.hostRadio -> { R.id.hostRadio -> {
@@ -80,6 +94,12 @@ class LobbyFragment : Fragment() {
} }
private fun observeViewModel() { private fun observeViewModel() {
viewModel.savedUserName.observe(viewLifecycleOwner) { savedName ->
if (!savedName.isNullOrEmpty() && binding.nameInput.text.isNullOrEmpty()) {
binding.nameInput.setText(savedName)
}
}
viewModel.state.observe(viewLifecycleOwner) { state -> viewModel.state.observe(viewLifecycleOwner) { state ->
when (state) { when (state) {
is LobbyState.Idle -> { is LobbyState.Idle -> {

View File

@@ -0,0 +1,36 @@
package com.mattintech.lchat.utils
import android.content.Context
import android.content.SharedPreferences
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class PreferencesManager @Inject constructor(
@ApplicationContext private val context: Context
) {
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
fun saveUserName(name: String) {
if (name.isBlank()) {
clearUserName()
} else {
sharedPreferences.edit().putString(KEY_USER_NAME, name).apply()
}
}
fun getUserName(): String? {
return sharedPreferences.getString(KEY_USER_NAME, null)
}
fun clearUserName() {
sharedPreferences.edit().remove(KEY_USER_NAME).apply()
}
companion object {
private const val PREFS_NAME = "lchat_preferences"
private const val KEY_USER_NAME = "user_name"
}
}

View File

@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.mattintech.lchat.repository.ChatRepository import com.mattintech.lchat.repository.ChatRepository
import com.mattintech.lchat.utils.PreferencesManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@@ -27,7 +28,8 @@ sealed class LobbyEvent {
@HiltViewModel @HiltViewModel
class LobbyViewModel @Inject constructor( class LobbyViewModel @Inject constructor(
private val chatRepository: ChatRepository private val chatRepository: ChatRepository,
private val preferencesManager: PreferencesManager
) : ViewModel() { ) : ViewModel() {
private val _state = MutableLiveData<LobbyState>(LobbyState.Idle) private val _state = MutableLiveData<LobbyState>(LobbyState.Idle)
@@ -36,8 +38,16 @@ class LobbyViewModel @Inject constructor(
private val _events = MutableLiveData<LobbyEvent?>() private val _events = MutableLiveData<LobbyEvent?>()
val events: LiveData<LobbyEvent?> = _events val events: LiveData<LobbyEvent?> = _events
private val _savedUserName = MutableLiveData<String?>()
val savedUserName: LiveData<String?> = _savedUserName
init { init {
setupConnectionCallback() setupConnectionCallback()
loadSavedUserName()
}
private fun loadSavedUserName() {
_savedUserName.value = preferencesManager.getUserName()
} }
private fun setupConnectionCallback() { private fun setupConnectionCallback() {
@@ -65,6 +75,7 @@ class LobbyViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
_state.value = LobbyState.Connecting _state.value = LobbyState.Connecting
preferencesManager.saveUserName(userName)
chatRepository.startHostMode(roomName) chatRepository.startHostMode(roomName)
_events.value = LobbyEvent.NavigateToChat(roomName, userName, true) _events.value = LobbyEvent.NavigateToChat(roomName, userName, true)
} }
@@ -78,11 +89,13 @@ class LobbyViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
_state.value = LobbyState.Connecting _state.value = LobbyState.Connecting
preferencesManager.saveUserName(userName)
chatRepository.startClientMode() chatRepository.startClientMode()
} }
} }
fun onConnectedToRoom(roomName: String, userName: String) { fun onConnectedToRoom(roomName: String, userName: String) {
preferencesManager.saveUserName(userName)
_events.value = LobbyEvent.NavigateToChat(roomName, userName, false) _events.value = LobbyEvent.NavigateToChat(roomName, userName, false)
} }
@@ -90,6 +103,12 @@ class LobbyViewModel @Inject constructor(
_events.value = null _events.value = null
} }
fun saveUserName(name: String) {
if (name.isNotBlank()) {
preferencesManager.saveUserName(name)
}
}
override fun onCleared() { override fun onCleared() {
super.onCleared() super.onCleared()
// Clean up resources if needed // Clean up resources if needed