WIP
This commit is contained in:
parent
1807d6145f
commit
19b61c959b
6
.idea/assetWizardSettings.xml
generated
6
.idea/assetWizardSettings.xml
generated
@ -18,7 +18,7 @@
|
|||||||
<PersistentState>
|
<PersistentState>
|
||||||
<option name="values">
|
<option name="values">
|
||||||
<map>
|
<map>
|
||||||
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/search/baseline_search_24.xml" />
|
<entry key="url" value="file:$USER_HOME$/Android/Sdk/icons/material/materialicons/mic/baseline_mic_24.xml" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</PersistentState>
|
</PersistentState>
|
||||||
@ -28,8 +28,8 @@
|
|||||||
</option>
|
</option>
|
||||||
<option name="values">
|
<option name="values">
|
||||||
<map>
|
<map>
|
||||||
<entry key="outputName" value="baseline_search_24" />
|
<entry key="outputName" value="baseline_mic_24" />
|
||||||
<entry key="sourceFile" value="C:\Users\erike\Downloads\comments-solid.svg" />
|
<entry key="sourceFile" value="$USER_HOME$/C:\Users\erike\Downloads\comments-solid.svg" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</PersistentState>
|
</PersistentState>
|
||||||
|
|||||||
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KotlinJpsPluginSettings">
|
||||||
|
<option name="version" value="1.8.0" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
242
.idea/navEditor.xml
generated
Normal file
242
.idea/navEditor.xml
generated
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="navEditor-manualLayoutAlgorithm2">
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="audio_recorder_navigation.xml">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="audioDraftsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="130" />
|
||||||
|
<option name="y" value="18" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="audioRecorderMainFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="-74" />
|
||||||
|
<option name="y" value="17" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="home_navigation.xml">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="feedFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="-280" />
|
||||||
|
<option name="y" value="-77" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="action_feedFragment_to_profileActivity">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="notificationsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="-282" />
|
||||||
|
<option name="y" value="-368" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="action_notificationsFragment_to_profileActivity">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="profileActivity">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="31" />
|
||||||
|
<option name="y" value="-367" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="searchFragment2">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="-281" />
|
||||||
|
<option name="y" value="221" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="profile_navigation.xml">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="audiosFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="256" />
|
||||||
|
<option name="y" value="12" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="discussionsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="12" />
|
||||||
|
<option name="y" value="12" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="imagesFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="12" />
|
||||||
|
<option name="y" value="368" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="settings_navigation.xml">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="changePasswordFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="256" />
|
||||||
|
<option name="y" value="368" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="feedSettingsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="256" />
|
||||||
|
<option name="y" value="12" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="sessionsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="256" />
|
||||||
|
<option name="y" value="724" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="settingsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions>
|
||||||
|
<option name="myPosition">
|
||||||
|
<Point>
|
||||||
|
<option name="x" value="12" />
|
||||||
|
<option name="y" value="76" />
|
||||||
|
</Point>
|
||||||
|
</option>
|
||||||
|
<option name="myPositions">
|
||||||
|
<map>
|
||||||
|
<entry key="action_settingsFragment_to_changePasswordFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="action_settingsFragment_to_feedSettingsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="action_settingsFragment_to_sessionsFragment">
|
||||||
|
<value>
|
||||||
|
<LayoutPositions />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</LayoutPositions>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@ -4,6 +4,7 @@ plugins {
|
|||||||
id 'kotlin-kapt'
|
id 'kotlin-kapt'
|
||||||
id 'com.google.dagger.hilt.android'
|
id 'com.google.dagger.hilt.android'
|
||||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.0'
|
id 'org.jetbrains.kotlin.plugin.serialization' version '1.8.0'
|
||||||
|
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -89,6 +90,19 @@ dependencies {
|
|||||||
implementation "io.noties.markwon:editor:$markwon_version"
|
implementation "io.noties.markwon:editor:$markwon_version"
|
||||||
implementation "io.noties.markwon:image-picasso:$markwon_version"
|
implementation "io.noties.markwon:image-picasso:$markwon_version"
|
||||||
implementation "io.noties.markwon:linkify:$markwon_version"
|
implementation "io.noties.markwon:linkify:$markwon_version"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
implementation ('io.socket:socket.io-client:2.1.0') {
|
||||||
|
// excluding org.json which is provided by Android
|
||||||
|
exclude group: 'org.json', module: 'json'
|
||||||
|
}
|
||||||
|
|
||||||
|
def room_version = "2.5.2"
|
||||||
|
|
||||||
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
|
|||||||
@ -11,11 +11,12 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Isolaatti"
|
android:theme="@style/Theme.Isolaatti"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Isolaatti.Splash" android:noHistory="true">
|
android:theme="@style/Theme.Isolaatti.Splash">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
package com.isolaatti
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.isolaatti.connectivity.SocketIO
|
||||||
|
|
||||||
|
class ActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
|
||||||
|
|
||||||
|
var startedActivitiesCount = 0
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
|
||||||
|
startedActivitiesCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityPaused(activity: Activity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStopped(activity: Activity) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
|
startedActivitiesCount--
|
||||||
|
|
||||||
|
if(startedActivitiesCount == 0) {
|
||||||
|
SocketIO.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
package com.isolaatti
|
package com.isolaatti
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import com.isolaatti.auth.data.AuthRepositoryImpl
|
import com.isolaatti.auth.data.AuthRepositoryImpl
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.HomeActivity
|
||||||
@ -15,6 +17,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var authRepository: AuthRepositoryImpl
|
lateinit var authRepository: AuthRepositoryImpl
|
||||||
|
|
||||||
|
private val signInActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
|
||||||
|
if(activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
val homeActivityIntent = Intent(this@MainActivity, HomeActivity::class.java)
|
||||||
|
homeActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
startActivity(homeActivityIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
var isLoading = true
|
var isLoading = true
|
||||||
val splashScreen = installSplashScreen()
|
val splashScreen = installSplashScreen()
|
||||||
@ -25,11 +35,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
val currentToken = authRepository.getCurrentToken()
|
val currentToken = authRepository.getCurrentToken()
|
||||||
|
|
||||||
if(currentToken == null) {
|
if(currentToken == null) {
|
||||||
startActivity(Intent(this@MainActivity, LogInActivity::class.java).apply {
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NO_HISTORY
|
signInActivityResult.launch(Intent(this@MainActivity, LogInActivity::class.java))
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
startActivity(Intent(this@MainActivity, HomeActivity::class.java))
|
val homeActivityIntent = Intent(this@MainActivity, HomeActivity::class.java)
|
||||||
|
homeActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
startActivity(homeActivityIntent)
|
||||||
}
|
}
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
package com.isolaatti
|
package com.isolaatti
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.os.Bundle
|
||||||
import com.isolaatti.auth.data.AuthRepositoryImpl
|
import com.isolaatti.auth.data.AuthRepositoryImpl
|
||||||
import com.isolaatti.auth.data.local.TokenStorage
|
import com.isolaatti.auth.data.local.TokenStorage
|
||||||
import com.isolaatti.auth.data.remote.AuthApi
|
import com.isolaatti.auth.data.remote.AuthApi
|
||||||
@ -13,7 +15,15 @@ import javax.inject.Singleton
|
|||||||
@HiltAndroidApp
|
@HiltAndroidApp
|
||||||
class MyApplication : Application() {
|
class MyApplication : Application() {
|
||||||
|
|
||||||
|
private val activityLifecycleCallbacks = ActivityLifecycleCallbacks()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTerminate() {
|
||||||
|
super.onTerminate()
|
||||||
|
unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package com.isolaatti.audio.recorder.ui
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
class AudioDraftsFragment : Fragment() {
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package com.isolaatti.audio.recorder.ui
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
|
||||||
|
class AudioRecorderActivity : ComponentActivity() {
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package com.isolaatti.audio.recorder.ui
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
class AudioRecorderMainFragment : Fragment() {
|
||||||
|
}
|
||||||
@ -1,18 +1,16 @@
|
|||||||
package com.isolaatti.auth.data
|
package com.isolaatti.auth.data
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.isolaatti.auth.data.remote.AuthTokenDto
|
import com.isolaatti.auth.data.remote.AuthTokenDto
|
||||||
import com.isolaatti.auth.data.local.TokenStorage
|
import com.isolaatti.auth.data.local.TokenStorage
|
||||||
import com.isolaatti.auth.data.remote.AuthApi
|
import com.isolaatti.auth.data.remote.AuthApi
|
||||||
import com.isolaatti.auth.data.remote.Credential
|
import com.isolaatti.auth.data.remote.Credential
|
||||||
import com.isolaatti.auth.domain.AuthRepository
|
import com.isolaatti.auth.domain.AuthRepository
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.launch
|
import retrofit2.await
|
||||||
import retrofit2.awaitResponse
|
import retrofit2.awaitResponse
|
||||||
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
@ -20,22 +18,41 @@ class AuthRepositoryImpl @Inject constructor(
|
|||||||
private val tokenStorage: TokenStorage,
|
private val tokenStorage: TokenStorage,
|
||||||
private val authApi: AuthApi
|
private val authApi: AuthApi
|
||||||
) : AuthRepository {
|
) : AuthRepository {
|
||||||
override fun authWithEmailAndPassword(email: String, password: String): Flow<Boolean> = flow {
|
override fun authWithEmailAndPassword(
|
||||||
val res = authApi.signInWithEmailAndPassword(Credential(email, password)).awaitResponse()
|
email: String,
|
||||||
val authDto = res.body()
|
password: String
|
||||||
|
): Flow<Resource<Boolean>> = flow {
|
||||||
|
try {
|
||||||
|
val res =
|
||||||
|
authApi.signInWithEmailAndPassword(Credential(email, password)).awaitResponse()
|
||||||
|
|
||||||
|
if(res.isSuccessful) {
|
||||||
|
val dto = res.body()
|
||||||
|
if(dto == null) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
tokenStorage.storeToken(dto)
|
||||||
|
emit(Resource.Success(true))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
|
||||||
if(authDto != null) {
|
when(res.code()){
|
||||||
tokenStorage.storeToken(authDto)
|
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||||
emit(true)
|
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||||
} else {
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
emit(false)
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logout(): Flow<Boolean> = flow {
|
override fun logout(): Flow<Boolean> = flow {
|
||||||
tokenStorage.removeToken()
|
tokenStorage.removeToken()
|
||||||
authApi.signOut()
|
try {
|
||||||
|
authApi.signOut().awaitResponse()
|
||||||
|
} catch(_: Exception) { }
|
||||||
emit(true)
|
emit(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -79,7 +79,9 @@ class TokenStorage @Inject constructor(@ApplicationContext private val applicati
|
|||||||
|
|
||||||
fun removeToken() {
|
fun removeToken() {
|
||||||
if(file.exists()) {
|
if(file.exists()) {
|
||||||
|
try {
|
||||||
file.delete()
|
file.delete()
|
||||||
|
} catch(_: SecurityException) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package com.isolaatti.auth.data.remote
|
package com.isolaatti.auth.data.remote
|
||||||
|
|
||||||
data class AuthTokenDto(val expires: String, val created: String, val token: String) {
|
data class AuthTokenDto(val token: String) {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
package com.isolaatti.auth.domain
|
package com.isolaatti.auth.domain
|
||||||
|
|
||||||
import com.isolaatti.auth.data.remote.AuthTokenDto
|
import com.isolaatti.auth.data.remote.AuthTokenDto
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AuthRepository {
|
interface AuthRepository {
|
||||||
fun authWithEmailAndPassword(email: String, password: String): Flow<Boolean>
|
fun authWithEmailAndPassword(email: String, password: String): Flow<Resource<Boolean>>
|
||||||
fun logout(): Flow<Boolean>
|
fun logout(): Flow<Boolean>
|
||||||
fun getCurrentToken(): AuthTokenDto?
|
fun getCurrentToken(): AuthTokenDto?
|
||||||
}
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.isolaatti.common
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
|
||||||
|
class ErrorMessageViewModel : ViewModel() {
|
||||||
|
val error: MutableLiveData<Resource.Error.ErrorType> = MutableLiveData()
|
||||||
|
}
|
||||||
@ -0,0 +1,81 @@
|
|||||||
|
package com.isolaatti.common
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.home.HomeActivity
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
val errorViewModel: ErrorMessageViewModel by viewModels()
|
||||||
|
|
||||||
|
private val errorObserver: Observer<Resource.Error.ErrorType> = Observer {
|
||||||
|
when(it) {
|
||||||
|
Resource.Error.ErrorType.AuthError -> showReAuthDialog()
|
||||||
|
Resource.Error.ErrorType.NetworkError -> showNetworkErrorMessage()
|
||||||
|
Resource.Error.ErrorType.NotFoundError -> showNotFoundErrorMessage()
|
||||||
|
Resource.Error.ErrorType.ServerError -> showServerErrorMessage()
|
||||||
|
Resource.Error.ErrorType.OtherError -> showUnknownErrorMessage()
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val signInActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
|
||||||
|
if(activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
onRetry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called when a refresh should be performed. For example,
|
||||||
|
* when sign in flow is started completed from here, it is needed to know
|
||||||
|
* when it is complete.
|
||||||
|
*/
|
||||||
|
abstract fun onRetry()
|
||||||
|
|
||||||
|
private val onAcceptReAuthClick = DialogInterface.OnClickListener { _, _ ->
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showReAuthDialog() {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(R.string.need_reauth_message)
|
||||||
|
.setPositiveButton(R.string.accept, onAcceptReAuthClick)
|
||||||
|
.setNegativeButton(R.string.close, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNetworkErrorMessage() {
|
||||||
|
Toast.makeText(this, R.string.network_error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showServerErrorMessage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNotFoundErrorMessage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUnknownErrorMessage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
errorViewModel.error.observe(this, errorObserver)
|
||||||
|
Log.d("IsolaattiBaseActivity", errorViewModel.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.connectivity
|
package com.isolaatti.connectivity
|
||||||
|
|
||||||
|
import com.isolaatti.BuildConfig
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
@ -14,7 +15,7 @@ class RetrofitClient @Inject constructor(private val authenticationInterceptor:
|
|||||||
val excludedUrlsFromAuthentication = listOf(
|
val excludedUrlsFromAuthentication = listOf(
|
||||||
"/api/LogIn"
|
"/api/LogIn"
|
||||||
)
|
)
|
||||||
const val BASE_URL = "https://isolaatti.com/api/"
|
const val BASE_URL = "${BuildConfig.backend}/api/"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val okHttpClient get() = OkHttpClient.Builder()
|
private val okHttpClient get() = OkHttpClient.Builder()
|
||||||
|
|||||||
26
app/src/main/java/com/isolaatti/connectivity/SocketIO.kt
Normal file
26
app/src/main/java/com/isolaatti/connectivity/SocketIO.kt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package com.isolaatti.connectivity
|
||||||
|
|
||||||
|
import io.socket.client.IO
|
||||||
|
import io.socket.client.Socket
|
||||||
|
|
||||||
|
object SocketIO {
|
||||||
|
private lateinit var socket: Socket
|
||||||
|
val instance: Socket get() {
|
||||||
|
return if(this::socket.isInitialized) {
|
||||||
|
if(socket.connected()) {
|
||||||
|
socket
|
||||||
|
} else {
|
||||||
|
IO.socket("")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
socket = IO.socket("")
|
||||||
|
socket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
if(this::socket.isInitialized) {
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +0,0 @@
|
|||||||
package com.isolaatti.feed.data.remote
|
|
||||||
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.http.GET
|
|
||||||
import retrofit2.http.Query
|
|
||||||
|
|
||||||
interface FeedApi {
|
|
||||||
@GET("Feed")
|
|
||||||
fun fetchFeed(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>
|
|
||||||
}
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
package com.isolaatti.feed.data.remote
|
|
||||||
|
|
||||||
data class FeedDto(
|
|
||||||
val data: MutableList<PostDto>,
|
|
||||||
val moreContent: Boolean
|
|
||||||
)
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package com.isolaatti.feed.data.remote
|
|
||||||
|
|
||||||
import com.isolaatti.feed.domain.model.Post
|
|
||||||
|
|
||||||
data class PostDto(
|
|
||||||
val post: Post,
|
|
||||||
var numberOfLikes: Int,
|
|
||||||
var numberOfComments: Int,
|
|
||||||
val userName: String,
|
|
||||||
val squadName: String?,
|
|
||||||
var liked: Boolean
|
|
||||||
)
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
package com.isolaatti.feed.data.repository
|
|
||||||
|
|
||||||
import com.isolaatti.feed.data.remote.FeedApi
|
|
||||||
import com.isolaatti.feed.data.remote.FeedDto
|
|
||||||
import com.isolaatti.feed.domain.repository.FeedRepository
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.flow
|
|
||||||
import retrofit2.awaitResponse
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class FeedRepositoryImpl @Inject constructor(private val feedApi: FeedApi): FeedRepository {
|
|
||||||
override fun getNextPage(lastId: Long, count: Int): Flow<FeedDto?> = flow {
|
|
||||||
val response = feedApi.fetchFeed(lastId, count).awaitResponse().body()
|
|
||||||
if(response != null){
|
|
||||||
emit(response)
|
|
||||||
} else {
|
|
||||||
emit(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
package com.isolaatti.feed.domain.model
|
|
||||||
|
|
||||||
data class Post(
|
|
||||||
val id: Long,
|
|
||||||
var textContent: String,
|
|
||||||
val userId: Int,
|
|
||||||
val privacy: Int,
|
|
||||||
val date: String,
|
|
||||||
var audioId: String,
|
|
||||||
val squadId: String,
|
|
||||||
val linkedDiscussionId: Long,
|
|
||||||
val linkedCommentId: Long
|
|
||||||
)
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
package com.isolaatti.feed.domain.repository
|
|
||||||
|
|
||||||
import com.isolaatti.feed.data.remote.FeedDto
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
interface FeedRepository {
|
|
||||||
fun getNextPage(lastId: Long, count: Int): Flow<FeedDto?>
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.isolaatti.feed.ui
|
package com.isolaatti.home
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -10,6 +10,7 @@ import android.view.ViewGroup
|
|||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
import com.isolaatti.databinding.FragmentFeedBinding
|
import com.isolaatti.databinding.FragmentFeedBinding
|
||||||
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
||||||
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
||||||
@ -34,6 +35,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val viewModel: PostsViewModel by activityViewModels()
|
private val viewModel: PostsViewModel by activityViewModels()
|
||||||
|
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
private lateinit var viewBinding: FragmentFeedBinding
|
private lateinit var viewBinding: FragmentFeedBinding
|
||||||
private lateinit var adapter: PostsRecyclerViewAdapter
|
private lateinit var adapter: PostsRecyclerViewAdapter
|
||||||
|
|
||||||
@ -67,6 +69,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val markwon = Markwon.builder(requireContext())
|
val markwon = Markwon.builder(requireContext())
|
||||||
.usePlugin(object: AbstractMarkwonPlugin() {
|
.usePlugin(object: AbstractMarkwonPlugin() {
|
||||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
@ -78,20 +81,32 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
|
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
|
||||||
.usePlugin(LinkifyPlugin.create())
|
.usePlugin(LinkifyPlugin.create())
|
||||||
.build()
|
.build()
|
||||||
adapter = PostsRecyclerViewAdapter(markwon, this, listOf())
|
adapter = PostsRecyclerViewAdapter(markwon, this, null)
|
||||||
viewBinding.feedRecyclerView.adapter = adapter
|
viewBinding.feedRecyclerView.adapter = adapter
|
||||||
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||||
|
|
||||||
|
|
||||||
viewModel.posts.observe(viewLifecycleOwner){
|
viewModel.posts.observe(viewLifecycleOwner){
|
||||||
Log.d("recycler", it.data.toString())
|
Log.d("recycler", it.data.toString())
|
||||||
adapter.updateList(it.data.toList(),null)
|
adapter.updateList(it,null)
|
||||||
|
}
|
||||||
|
Log.d("FeedFragment", errorViewModel.toString())
|
||||||
|
viewModel.errorLoading.observe(viewLifecycleOwner) {
|
||||||
|
errorViewModel.error.postValue(it)
|
||||||
|
Log.d("FeedFragment", it.toString())
|
||||||
}
|
}
|
||||||
viewModel.postLiked.observe(viewLifecycleOwner) {
|
viewModel.postLiked.observe(viewLifecycleOwner) {
|
||||||
adapter.updateList(viewModel.posts.value?.data, PostsRecyclerViewAdapter.UpdateEvent(
|
viewModel.posts.value?.let { feed ->
|
||||||
|
adapter.updateList(
|
||||||
|
feed, PostsRecyclerViewAdapter.UpdateEvent(
|
||||||
PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId))
|
PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNewMenuItemClicked(v: View) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
||||||
|
|
||||||
@ -4,17 +4,24 @@ import android.os.Bundle
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
|
import com.isolaatti.common.IsolaattiBaseActivity
|
||||||
import com.isolaatti.databinding.ActivityHomeBinding
|
import com.isolaatti.databinding.ActivityHomeBinding
|
||||||
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
class HomeActivity : IsolaattiBaseActivity() {
|
||||||
class HomeActivity : AppCompatActivity() {
|
private lateinit var viewBinding: ActivityHomeBinding
|
||||||
lateinit var viewBinding: ActivityHomeBinding
|
private val postsViewModel: PostsViewModel by viewModels()
|
||||||
val postsViewModel: PostsViewModel by viewModels()
|
override fun onRetry() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -33,4 +40,8 @@ class HomeActivity : AppCompatActivity() {
|
|||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,27 +0,0 @@
|
|||||||
package com.isolaatti.home
|
|
||||||
|
|
||||||
import com.isolaatti.connectivity.RetrofitClient
|
|
||||||
import com.isolaatti.feed.data.remote.FeedApi
|
|
||||||
import com.isolaatti.feed.data.repository.FeedRepositoryImpl
|
|
||||||
import com.isolaatti.feed.domain.repository.FeedRepository
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
|
||||||
import dagger.hilt.android.scopes.ActivityScoped
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
class Module {
|
|
||||||
@Provides
|
|
||||||
fun provideFeedApi(retrofitClient: RetrofitClient): FeedApi {
|
|
||||||
return retrofitClient.client.create(FeedApi::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideFeedRepository(feedApi: FeedApi): FeedRepository {
|
|
||||||
return FeedRepositoryImpl(feedApi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package com.isolaatti.home.search.presentation
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import com.isolaatti.databinding.FragmentSearchBinding
|
||||||
|
|
||||||
|
class SearchFragment : Fragment() {
|
||||||
|
|
||||||
|
lateinit var viewBinding: FragmentSearchBinding
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
viewBinding = FragmentSearchBinding.inflate(inflater)
|
||||||
|
return viewBinding.root
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,31 @@
|
|||||||
package com.isolaatti.login
|
package com.isolaatti.login
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.isolaatti.BuildConfig
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.common.IsolaattiBaseActivity
|
||||||
import com.isolaatti.databinding.ActivityLoginBinding
|
import com.isolaatti.databinding.ActivityLoginBinding
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.HomeActivity
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class LogInActivity: AppCompatActivity() {
|
class LogInActivity: AppCompatActivity() {
|
||||||
|
|
||||||
|
|
||||||
lateinit var viewBinding: ActivityLoginBinding
|
lateinit var viewBinding: ActivityLoginBinding
|
||||||
val viewModel: LogInViewModel by viewModels()
|
private val viewModel: LogInViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -23,11 +34,33 @@ class LogInActivity: AppCompatActivity() {
|
|||||||
setContentView(viewBinding.root)
|
setContentView(viewBinding.root)
|
||||||
|
|
||||||
viewModel.signInSuccess.observe(this) {success ->
|
viewModel.signInSuccess.observe(this) {success ->
|
||||||
if(success)
|
if(success) {
|
||||||
startActivity(Intent(this, HomeActivity::class.java))
|
setResult(Activity.RESULT_OK)
|
||||||
else
|
finish()
|
||||||
Toast.makeText(this,"Could not sign in, your credential is not correct...", Toast.LENGTH_SHORT).show()
|
}
|
||||||
// Show login error message.
|
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.signInLoading.observe(this) {
|
||||||
|
viewBinding.progressBar.visibility = View.VISIBLE
|
||||||
|
viewBinding.signInBtn.isEnabled = false
|
||||||
|
viewBinding.textFieldEmail.isEnabled = false
|
||||||
|
viewBinding.textFieldPassword.isEnabled = false
|
||||||
|
}
|
||||||
|
viewModel.signInError.observe(this) {
|
||||||
|
viewBinding.progressBar.visibility = View.GONE
|
||||||
|
viewBinding.signInBtn.isEnabled = true
|
||||||
|
viewBinding.textFieldEmail.isEnabled = true
|
||||||
|
viewBinding.textFieldPassword.isEnabled = true
|
||||||
|
when(it) {
|
||||||
|
Resource.Error.ErrorType.NetworkError -> showNetworkErrorMessage()
|
||||||
|
Resource.Error.ErrorType.AuthError -> showWrongPasswordErrorMessage()
|
||||||
|
Resource.Error.ErrorType.NotFoundError -> showNotFoundErrorMessage()
|
||||||
|
Resource.Error.ErrorType.ServerError -> showServerErrorMessage()
|
||||||
|
Resource.Error.ErrorType.OtherError -> showUnknownErrorMessage()
|
||||||
|
null -> {}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.formIsValid.observe(this) {isValid ->
|
viewModel.formIsValid.observe(this) {isValid ->
|
||||||
@ -50,5 +83,42 @@ class LogInActivity: AppCompatActivity() {
|
|||||||
viewModel.signIn(email.toString(), password.toString())
|
viewModel.signIn(email.toString(), password.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewBinding.forgotPasswordBtn.setOnClickListener {
|
||||||
|
openForgotPassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openForgotPassword() {
|
||||||
|
CustomTabsIntent.Builder()
|
||||||
|
.setShowTitle(true)
|
||||||
|
.build()
|
||||||
|
.launchUrl(this, Uri.parse("${BuildConfig.backend}/recuperacion_cuenta"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showWrongPasswordErrorMessage() {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(R.string.wrong_password)
|
||||||
|
.setNeutralButton(R.string.forgot_password) {_,_ -> openForgotPassword()}
|
||||||
|
.setPositiveButton(R.string.dismiss, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
private fun showNetworkErrorMessage() {
|
||||||
|
Toast.makeText(this, R.string.network_error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showServerErrorMessage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNotFoundErrorMessage() {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(getString(R.string.account_not_found, viewBinding.textFieldEmail.editText?.text))
|
||||||
|
.setPositiveButton(R.string.dismiss, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUnknownErrorMessage() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,16 +5,22 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.isolaatti.auth.domain.AuthRepository
|
import com.isolaatti.auth.domain.AuthRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class LogInViewModel @Inject constructor(private val authRepository: AuthRepository): ViewModel() {
|
class LogInViewModel @Inject constructor(private val authRepository: AuthRepository): ViewModel() {
|
||||||
val signInSuccess: MutableLiveData<Boolean> = MutableLiveData()
|
val signInSuccess: MutableLiveData<Boolean> = MutableLiveData()
|
||||||
|
val signInError: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
||||||
|
val signInLoading: MutableLiveData<Boolean> = MutableLiveData()
|
||||||
val formIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
val formIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
val emailUserInputIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
val emailUserInputIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
val passwordUserInputIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
val passwordUserInputIsValid: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
@ -33,11 +39,23 @@ class LogInViewModel @Inject constructor(private val authRepository: AuthReposit
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun signIn(email: String, password: String) {
|
fun signIn(email: String, password: String) {
|
||||||
|
signInError.postValue(null)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
authRepository.authWithEmailAndPassword(email, password).collect {
|
authRepository.authWithEmailAndPassword(email, password).onEach {
|
||||||
Log.d("login", it.toString())
|
Log.d("login", it.toString())
|
||||||
signInSuccess.postValue(it)
|
when(it) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
signInLoading.postValue(false)
|
||||||
|
signInSuccess.postValue(true)
|
||||||
}
|
}
|
||||||
|
is Resource.Error -> {
|
||||||
|
signInLoading.postValue(false)
|
||||||
|
signInError.postValue(it.errorType)
|
||||||
|
}
|
||||||
|
is Resource.Loading -> signInLoading.postValue(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package com.isolaatti.posting
|
package com.isolaatti.posting
|
||||||
|
|
||||||
import com.isolaatti.connectivity.RetrofitClient
|
import com.isolaatti.connectivity.RetrofitClient
|
||||||
import com.isolaatti.posting.posts.data.remote.PostsApi
|
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
||||||
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
||||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@ -13,12 +13,12 @@ import dagger.hilt.components.SingletonComponent
|
|||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
class Module {
|
class Module {
|
||||||
@Provides
|
@Provides
|
||||||
fun providePostsApi(retrofitClient: RetrofitClient): PostsApi {
|
fun providePostsApi(retrofitClient: RetrofitClient): FeedsApi {
|
||||||
return retrofitClient.client.create(PostsApi::class.java)
|
return retrofitClient.client.create(FeedsApi::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun providePostsRepository(postsApi: PostsApi): PostsRepository {
|
fun providePostsRepository(feedsApi: FeedsApi): PostsRepository {
|
||||||
return PostsRepositoryImpl(postsApi)
|
return PostsRepositoryImpl(feedsApi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
|||||||
val textField = viewBinding.newCommentTextField
|
val textField = viewBinding.newCommentTextField
|
||||||
|
|
||||||
textField.setStartIconOnClickListener {
|
textField.setStartIconOnClickListener {
|
||||||
AlertDialog.Builder(requireContext()).setView(R.layout.write_comment_multiline_dialog).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optionsViewModel.optionClicked(-1)
|
optionsViewModel.optionClicked(-1)
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
data class FeedDto(
|
||||||
|
val data: MutableList<PostDto>,
|
||||||
|
var moreContent: Boolean
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun concatFeed(otherFeed: FeedDto?): FeedDto {
|
||||||
|
if(otherFeed != null) {
|
||||||
|
data.addAll(otherFeed.data)
|
||||||
|
moreContent = otherFeed.moreContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
data class PostDto(
|
||||||
|
val post: Post,
|
||||||
|
var numberOfLikes: Int,
|
||||||
|
var numberOfComments: Int,
|
||||||
|
val userName: String,
|
||||||
|
val squadName: String?,
|
||||||
|
var liked: Boolean
|
||||||
|
) {
|
||||||
|
data class Post(
|
||||||
|
val id: Long,
|
||||||
|
var textContent: String,
|
||||||
|
val userId: Int,
|
||||||
|
val privacy: Int,
|
||||||
|
val date: String,
|
||||||
|
var audioId: String,
|
||||||
|
val squadId: String,
|
||||||
|
val linkedDiscussionId: Long,
|
||||||
|
val linkedCommentId: Long
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
data class FeedFilterDto(
|
||||||
|
val includeAudio: String,
|
||||||
|
val includeFromSquads: String,
|
||||||
|
val dataRange: DataRange
|
||||||
|
) {
|
||||||
|
data class DataRange(
|
||||||
|
val enabled: Boolean,
|
||||||
|
val from: String,
|
||||||
|
val to: String
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,23 +1,25 @@
|
|||||||
package com.isolaatti.posting.posts.data.remote
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
import com.isolaatti.feed.data.remote.FeedDto
|
|
||||||
import com.isolaatti.feed.data.remote.PostDto
|
|
||||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface PostsApi {
|
interface FeedsApi {
|
||||||
@GET("Fetch/PostsOfUser/{userId}")
|
@GET("Fetch/PostsOfUser/{userId}")
|
||||||
fun postsOfUser(@Path("userId") userId: Int,
|
fun postsOfUser(@Path("userId") userId: Int,
|
||||||
@Query("length") length: Int,
|
@Query("length") length: Int,
|
||||||
@Query("lastId") lastId: Long,
|
@Query("lastId") lastId: Long,
|
||||||
@Query("olderFirst") olderFirst: Boolean): Call<FeedDto>
|
@Query("olderFirst") olderFirst: Boolean,
|
||||||
|
@Query(value = "filterJson", encoded = false) filter: String): Call<FeedDto>
|
||||||
|
|
||||||
@GET("Fetch/Post/{postId}")
|
@GET("Fetch/Post/{postId}")
|
||||||
fun getPost(@Path("postId") postId: Long): Call<PostDto>
|
fun getPost(@Path("postId") postId: Long): Call<FeedDto>
|
||||||
|
|
||||||
@GET("Fetch/Post/{postId}/LikedBy")
|
@GET("Fetch/Post/{postId}/LikedBy")
|
||||||
fun getLikedBy(@Path("postId") postId: Long): Call<List<ProfileListItemDto>>
|
fun getLikedBy(@Path("postId") postId: Long): Call<List<ProfileListItemDto>>
|
||||||
|
|
||||||
|
@GET("Feed")
|
||||||
|
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>
|
||||||
}
|
}
|
||||||
@ -1,8 +1,58 @@
|
|||||||
package com.isolaatti.posting.posts.data.repository
|
package com.isolaatti.posting.posts.data.repository
|
||||||
|
|
||||||
import com.isolaatti.posting.posts.data.remote.PostsApi
|
import com.google.gson.Gson
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
||||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.FlowCollector
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import retrofit2.await
|
||||||
|
import java.io.IOException
|
||||||
|
import java.lang.RuntimeException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
class PostsRepositoryImpl @Inject constructor(private val postsApi: PostsApi) : PostsRepository {
|
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi) : PostsRepository {
|
||||||
|
override fun getFeed(lastId: Long): Flow<Resource<FeedDto>> = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
|
try {
|
||||||
|
val result = feedsApi.getChronology(lastId, 20).execute()
|
||||||
|
if(result.isSuccessful) {
|
||||||
|
emit(Resource.Success(result.body()))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
when(result.code()) {
|
||||||
|
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||||
|
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||||
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
|
}
|
||||||
|
} catch(_: Exception) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>> = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
|
try {
|
||||||
|
val gson = Gson()
|
||||||
|
val result = feedsApi.postsOfUser(userId, 20, lastId, olderFirst, gson.toJson(filter)).execute()
|
||||||
|
if(result.isSuccessful) {
|
||||||
|
emit(Resource.Success(result.body()))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
when(result.code()) {
|
||||||
|
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||||
|
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||||
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
|
}
|
||||||
|
} catch(_: Exception) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,13 @@
|
|||||||
package com.isolaatti.posting.posts.domain
|
package com.isolaatti.posting.posts.domain
|
||||||
|
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface PostsRepository {
|
interface PostsRepository {
|
||||||
|
|
||||||
|
fun getFeed(lastId: Long): Flow<Resource<FeedDto>>
|
||||||
|
|
||||||
|
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>>
|
||||||
}
|
}
|
||||||
@ -11,15 +11,15 @@ import androidx.recyclerview.widget.RecyclerView
|
|||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.feed.data.remote.PostDto
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
||||||
import com.isolaatti.utils.UrlGen.userProfileImage
|
import com.isolaatti.utils.UrlGen.userProfileImage
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
|
|
||||||
class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback, private var list: List<PostDto>) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
|
class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback, private var feedDto: FeedDto?) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
|
||||||
inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) {
|
inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||||
fun bindView(postDto: PostDto, payloads: List<Any>) {
|
fun bindView(postDto: FeedDto.PostDto, payloads: List<Any>) {
|
||||||
|
|
||||||
Log.d("payloads", payloads.count().toString())
|
Log.d("payloads", payloads.count().toString())
|
||||||
val likeButton: MaterialButton = itemView.findViewById(R.id.like_button)
|
val likeButton: MaterialButton = itemView.findViewById(R.id.like_button)
|
||||||
@ -120,29 +120,24 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
|
|||||||
|
|
||||||
return FeedViewHolder(view)
|
return FeedViewHolder(view)
|
||||||
}
|
}
|
||||||
override fun getItemCount(): Int = list.size
|
override fun getItemCount(): Int = feedDto?.data?.size ?: 0
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long = list[position].post.id
|
|
||||||
|
|
||||||
override fun setHasStableIds(hasStableIds: Boolean) {
|
override fun setHasStableIds(hasStableIds: Boolean) {
|
||||||
super.setHasStableIds(true)
|
super.setHasStableIds(true)
|
||||||
}
|
}
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
fun updateList(newList: List<PostDto>?, updateEvent: UpdateEvent? = null) {
|
fun updateList(updatedFeed: FeedDto, updateEvent: UpdateEvent? = null) {
|
||||||
if(updateEvent == null) {
|
if(updateEvent == null) {
|
||||||
if(newList != null) {
|
feedDto = updatedFeed
|
||||||
list = newList
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val postUpdated = list.find { p -> p.post.id == updateEvent.affectedId } ?: return
|
val postUpdated = feedDto?.data?.find { p -> p.post.id == updateEvent.affectedId } ?: return
|
||||||
val position = list.indexOf(postUpdated)
|
val position = feedDto?.data?.indexOf(postUpdated) ?: return
|
||||||
|
|
||||||
if(newList != null) {
|
feedDto = updatedFeed
|
||||||
list = newList
|
|
||||||
}
|
|
||||||
|
|
||||||
when(updateEvent.updateType) {
|
when(updateEvent.updateType) {
|
||||||
UpdateEvent.UpdateType.POST_LIKED -> {
|
UpdateEvent.UpdateType.POST_LIKED -> {
|
||||||
@ -163,6 +158,6 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
|
|||||||
override fun onBindViewHolder(holder: FeedViewHolder, position: Int) {}
|
override fun onBindViewHolder(holder: FeedViewHolder, position: Int) {}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: FeedViewHolder, position: Int, payloads: List<Any>) {
|
override fun onBindViewHolder(holder: FeedViewHolder, position: Int, payloads: List<Any>) {
|
||||||
holder.bindView(list[position], payloads)
|
holder.bindView(feedDto?.data?.get(position) ?: return, payloads)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,11 +6,14 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
|
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
|
||||||
import com.isolaatti.feed.data.remote.FeedDto
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
import com.isolaatti.feed.domain.repository.FeedRepository
|
|
||||||
import com.isolaatti.posting.likes.data.remote.LikeDto
|
import com.isolaatti.posting.likes.data.remote.LikeDto
|
||||||
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
||||||
|
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
||||||
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@ -19,11 +22,17 @@ import kotlinx.coroutines.launch
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class PostsViewModel @Inject constructor(private val feedRepository: FeedRepository, private val likesRepository: LikesRepository) : ViewModel() {
|
class PostsViewModel @Inject constructor(private val postsRepository: PostsRepository, private val likesRepository: LikesRepository) : ViewModel() {
|
||||||
|
|
||||||
private val _posts: MutableLiveData<FeedDto> = MutableLiveData()
|
private val _posts: MutableLiveData<FeedDto> = MutableLiveData()
|
||||||
val posts: LiveData<FeedDto> get() = _posts
|
val posts: LiveData<FeedDto> get() = _posts
|
||||||
|
|
||||||
|
private val _loadingPosts = MutableLiveData(false)
|
||||||
|
val loadingPosts: LiveData<Boolean> get() = _loadingPosts
|
||||||
|
|
||||||
|
private val _errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
||||||
|
val errorLoading: LiveData<Resource.Error.ErrorType?> get() = _errorLoading
|
||||||
|
|
||||||
private val _comments: MutableLiveData<FeedCommentsDto> = MutableLiveData()
|
private val _comments: MutableLiveData<FeedCommentsDto> = MutableLiveData()
|
||||||
val comments: LiveData<FeedCommentsDto> get() = _comments
|
val comments: LiveData<FeedCommentsDto> get() = _comments
|
||||||
|
|
||||||
@ -34,19 +43,19 @@ class PostsViewModel @Inject constructor(private val feedRepository: FeedReposit
|
|||||||
|
|
||||||
fun getFeed() {
|
fun getFeed() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
feedRepository.getNextPage(getLastId(), 20).onEach {feedDto ->
|
postsRepository.getFeed(getLastId()).onEach {
|
||||||
val temp = _posts.value
|
when(it) {
|
||||||
if(temp != null) {
|
is Resource.Success -> {
|
||||||
feedDto?.data?.let { it ->
|
_loadingPosts.postValue(false)
|
||||||
temp.data.addAll(it)
|
_posts.postValue(posts.value?.concatFeed(it.data) ?: it.data)
|
||||||
}
|
}
|
||||||
temp.let {
|
is Resource.Loading -> {
|
||||||
_posts.postValue(it)
|
_loadingPosts.postValue(true)
|
||||||
|
}
|
||||||
|
is Resource.Error -> {
|
||||||
|
_errorLoading.postValue(it.errorType)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
_posts.postValue(feedDto)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package com.isolaatti.profile.data.remote
|
package com.isolaatti.profile.data.remote
|
||||||
|
|
||||||
import com.isolaatti.feed.data.remote.FeedDto
|
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|||||||
@ -1,10 +1,19 @@
|
|||||||
package com.isolaatti.profile.presentation
|
package com.isolaatti.profile.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.isolaatti.profile.data.remote.UserProfileDto
|
||||||
import com.isolaatti.profile.domain.ProfileRepository
|
import com.isolaatti.profile.domain.ProfileRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ProfileViewModel @Inject constructor(private val profileRepository: ProfileRepository) : ViewModel() {
|
class ProfileViewModel @Inject constructor(private val profileRepository: ProfileRepository) : ViewModel() {
|
||||||
|
private val _profile = MutableLiveData<UserProfileDto>()
|
||||||
|
val profile: LiveData<UserProfileDto> get() = _profile
|
||||||
|
|
||||||
|
fun getProfile(profileId: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,15 +2,21 @@ package com.isolaatti.profile.ui
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.viewpager.widget.PagerAdapter
|
import androidx.viewpager.widget.PagerAdapter
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import androidx.viewpager2.adapter.FragmentViewHolder
|
import androidx.viewpager2.adapter.FragmentViewHolder
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.databinding.ActivityProfileBinding
|
import com.isolaatti.databinding.ActivityProfileBinding
|
||||||
|
import com.isolaatti.profile.data.remote.UserProfileDto
|
||||||
|
import com.isolaatti.profile.presentation.ProfileViewModel
|
||||||
|
import com.isolaatti.utils.UrlGen
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@ -38,19 +44,24 @@ class ProfileActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
|
|
||||||
lateinit var viewBinding: ActivityProfileBinding
|
lateinit var viewBinding: ActivityProfileBinding
|
||||||
|
private val viewModel: ProfileViewModel by viewModels()
|
||||||
|
|
||||||
|
private val profileObserver = Observer<UserProfileDto> { profile ->
|
||||||
|
Picasso.get()
|
||||||
|
.load(UrlGen.userProfileImage(profile.id))
|
||||||
|
.into(viewBinding.profileImageView)
|
||||||
|
|
||||||
|
viewBinding.textViewUsername.text = profile.name
|
||||||
|
viewBinding.textViewDescription.text = profile.descriptionText
|
||||||
|
}
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
viewBinding = ActivityProfileBinding.inflate(layoutInflater)
|
viewBinding = ActivityProfileBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
setContentView(viewBinding.root)
|
setContentView(viewBinding.root)
|
||||||
|
|
||||||
Picasso.get().load("https://isolaatti.com/api/images/image/63a2a6c5270ecc2be2512799?mode=reduced").into(viewBinding.profileImageView)
|
|
||||||
viewBinding.textViewUsername.text = "Erik Everardo"
|
|
||||||
viewBinding.textViewDescription.text = "Hola"
|
|
||||||
viewBinding.profileViewPager2.adapter = ViewPagerAdapter(this)
|
viewBinding.profileViewPager2.adapter = ViewPagerAdapter(this)
|
||||||
viewBinding.topAppBar.setNavigationOnClickListener {
|
viewBinding.topAppBar.setNavigationOnClickListener {
|
||||||
onBackPressed()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
TabLayoutMediator(viewBinding.profileTabLayout, viewBinding.profileViewPager2) {tab, position ->
|
TabLayoutMediator(viewBinding.profileTabLayout, viewBinding.profileViewPager2) {tab, position ->
|
||||||
@ -66,5 +77,11 @@ class ProfileActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
|
viewModel.profile.observe(this, profileObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_USER_ID = "user_id"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
package com.isolaatti.utils
|
package com.isolaatti.utils
|
||||||
|
|
||||||
class Resource<T> {
|
abstract class Resource<T> {
|
||||||
inner class Success(data: T)
|
class Success<T>(val data: T?): Resource<T>()
|
||||||
|
class Loading<T>: Resource<T>()
|
||||||
inner class Loading()
|
class Error<T>(val errorType: ErrorType? = null): Resource<T>() {
|
||||||
|
enum class ErrorType {
|
||||||
inner class Error
|
NetworkError, AuthError, NotFoundError, ServerError, OtherError
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/baseline_keyboard_voice_24.xml
Normal file
5
app/src/main/res/drawable/baseline_keyboard_voice_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,15c1.66,0 2.99,-1.34 2.99,-3L15,6c0,-1.66 -1.34,-3 -3,-3S9,4.34 9,6v6c0,1.66 1.34,3 3,3zM17.3,12c0,3 -2.54,5.1 -5.3,5.1S6.7,15 6.7,12L5,12c0,3.42 2.72,6.23 6,6.72L11,22h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/baseline_mic_24.xml
Normal file
5
app/src/main/res/drawable/baseline_mic_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/baseline_post_add_24.xml
Normal file
9
app/src/main/res/drawable/baseline_post_add_24.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17,19.22H5V7h7V5H5C3.9,5 3,5.9 3,7v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-7h-2V19.22z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,2h-2v3h-3c0.01,0.01 0,2 0,2h3v2.99c0.01,0.01 2,0 2,0V7h3V5h-3V2z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M7,9h8v2h-8z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M7,12l0,2l8,0l0,-2l-3,0z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M7,15h8v2h-8z"/>
|
||||||
|
</vector>
|
||||||
@ -17,7 +17,7 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:context=".feed.ui.FeedFragment">
|
tools:context=".home.FeedFragment">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -48,6 +48,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:title="@string/app_name"
|
app:title="@string/app_name"
|
||||||
app:titleCentered="true"
|
app:titleCentered="true"
|
||||||
|
app:menu="@menu/new_menu"
|
||||||
tools:layout_conversion_absoluteHeight="64dp"
|
tools:layout_conversion_absoluteHeight="64dp"
|
||||||
tools:layout_conversion_absoluteWidth="531dp"
|
tools:layout_conversion_absoluteWidth="531dp"
|
||||||
tools:layout_editor_absoluteX="360dp"
|
tools:layout_editor_absoluteX="360dp"
|
||||||
|
|||||||
@ -1,6 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView
|
<FrameLayout android:layout_width="match_parent"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:translationZ="10dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -89,4 +101,5 @@
|
|||||||
android:text="@string/sign_up"/>
|
android:text="@string/sign_up"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@ -6,47 +6,49 @@
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
<androidx.core.widget.NestedScrollView
|
android:id="@+id/topAppBar_layout"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:orientation="vertical">
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:id="@+id/topAppBar_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/top_app_bar"
|
android:id="@+id/top_app_bar"
|
||||||
|
style="@style/Theme.Isolaatti"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?attr/actionBarSize"
|
android:layout_height="?attr/actionBarSize"
|
||||||
app:titleCentered="true"
|
|
||||||
style="@style/Theme.Isolaatti"
|
|
||||||
app:navigationIcon="@drawable/baseline_arrow_back_24"
|
app:navigationIcon="@drawable/baseline_arrow_back_24"
|
||||||
app:title="Profile"/>
|
app:title="Profile"
|
||||||
|
app:titleCentered="true" />
|
||||||
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/topAppBar_layout">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/profile_image_view"
|
android:id="@+id/profile_image_view"
|
||||||
android:layout_width="120dp"
|
android:layout_width="120dp"
|
||||||
android:layout_height="120dp"
|
android:layout_height="120dp"
|
||||||
android:layout_weight="1"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
android:layout_marginTop="16dp"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||||
tools:srcCompat="@tools:sample/avatars" />
|
tools:srcCompat="@tools:sample/avatars" />
|
||||||
|
|
||||||
@ -54,30 +56,76 @@
|
|||||||
android:id="@+id/text_view_username"
|
android:id="@+id/text_view_username"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:layout_marginTop="20dp"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/profile_image_view"
|
||||||
tools:text="Erik Cavazos" />
|
tools:text="Erik Cavazos" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/text_view_description"
|
android:id="@+id/text_view_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="20dp"
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:maxLines="4"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
tools:text="Hi there, I am software developer!"/>
|
app:layout_constraintTop_toBottomOf="@id/text_view_username"
|
||||||
</LinearLayout>
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:text="Hi there, I am software developer!" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_view_following_state"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/text_view_description"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
tools:text="Following this profile" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
style="?attr/materialIconButtonFilledTonalStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/profile_options_button"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/text_view_following_state" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/profile_options_button"
|
||||||
|
style="?attr/materialIconButtonFilledTonalStyle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
app:icon="@drawable/baseline_more_horiz_24"
|
||||||
|
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/text_view_following_state"/>
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
<com.google.android.material.tabs.TabLayout
|
||||||
android:id="@+id/profile_tab_layout"
|
android:id="@+id/profile_tab_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
app:tabContentStart="56dp"
|
||||||
app:tabMode="fixed"
|
app:tabMode="fixed"
|
||||||
app:tabContentStart="56dp"/>
|
app:layout_constraintTop_toBottomOf="@id/profile_options_button"/>
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/profile_view_pager2"
|
android:id="@+id/profile_view_pager2"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
</LinearLayout>
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/profile_tab_layout"/>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
35
app/src/main/res/layout/audio_recorder_activity.xml
Normal file
35
app/src/main/res/layout/audio_recorder_activity.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/topAppBar_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/topAppBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:theme="@style/Theme.Isolaatti"
|
||||||
|
app:navigationIcon="@drawable/baseline_close_24"
|
||||||
|
app:navigationIconTint="@color/on_surface"
|
||||||
|
app:title="@string/audio_recorder"/>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
app:defaultNavHost="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/topAppBar_layout" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
6
app/src/main/res/layout/audio_recorder_main_fragment.xml
Normal file
6
app/src/main/res/layout/audio_recorder_main_fragment.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
app:boxCornerRadiusTopEnd="20dp"
|
app:boxCornerRadiusTopEnd="20dp"
|
||||||
app:boxCornerRadiusTopStart="20dp"
|
app:boxCornerRadiusTopStart="20dp"
|
||||||
|
|
||||||
app:startIconDrawable="@drawable/baseline_open_in_full_24"
|
app:startIconDrawable="@drawable/baseline_keyboard_voice_24"
|
||||||
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/submitCommentButton"
|
app:layout_constraintEnd_toStartOf="@+id/submitCommentButton"
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".feed.ui.FeedFragment">
|
tools:context=".home.FeedFragment">
|
||||||
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
@ -29,7 +29,8 @@
|
|||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/topAppBar_layout"
|
android:id="@+id/topAppBar_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
app:liftOnScroll="true">
|
||||||
|
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/topAppBar"
|
android:id="@+id/topAppBar"
|
||||||
@ -38,26 +39,23 @@
|
|||||||
android:theme="@style/Theme.Isolaatti"
|
android:theme="@style/Theme.Isolaatti"
|
||||||
app:navigationIcon="@drawable/baseline_menu_24"
|
app:navigationIcon="@drawable/baseline_menu_24"
|
||||||
app:navigationIconTint="@color/on_surface"
|
app:navigationIconTint="@color/on_surface"
|
||||||
|
app:menu="@menu/new_menu"
|
||||||
app:title="@string/app_name"
|
app:title="@string/app_name"
|
||||||
app:titleCentered="true" />
|
app:titleCentered="true" />
|
||||||
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" >
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/feed_recycler_view"
|
android:id="@+id/feed_recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/floating_action_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_margin="16dp"
|
|
||||||
android:contentDescription=""
|
|
||||||
app:srcCompat="@drawable/baseline_add_24" />
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView3"
|
android:id="@+id/textView3"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="2000dp"
|
||||||
android:text="@string/images"
|
android:text="@string/images"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</ScrollView>
|
||||||
|
</FrameLayout>
|
||||||
34
app/src/main/res/layout/fragment_search.xml
Normal file
34
app/src/main/res/layout/fragment_search.xml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<!-- NestedScrollingChild goes here (NestedScrollView, RecyclerView, etc.). -->
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layout_behavior="com.google.android.material.search.SearchBar$ScrollingViewBehavior">
|
||||||
|
<!-- Screen content goes here. -->
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<com.google.android.material.search.SearchBar
|
||||||
|
android:id="@+id/search_bar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/searchbar_hint" />
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.search.SearchView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:hint="@string/searchbar_hint"
|
||||||
|
app:layout_anchor="@id/search_bar">
|
||||||
|
<!-- Search suggestions/results go here (ScrollView, RecyclerView, etc.). -->
|
||||||
|
</com.google.android.material.search.SearchView>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@ -3,7 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:icon="@drawable/baseline_notifications_24"
|
android:icon="@drawable/baseline_add_24"
|
||||||
android:title="Notifications"
|
app:showAsAction="always"
|
||||||
app:showAsAction="always" />
|
android:title="@string/new_post" />
|
||||||
</menu>
|
</menu>
|
||||||
13
app/src/main/res/menu/new_menu.xml
Normal file
13
app/src/main/res/menu/new_menu.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item android:id="@+id/menu_item_new_discussion"
|
||||||
|
android:title="@string/discussion"
|
||||||
|
android:icon="@drawable/baseline_post_add_24"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
<item android:id="@+id/menu_item_new_audio"
|
||||||
|
android:title="@string/add_audio"
|
||||||
|
android:icon="@drawable/baseline_mic_24"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
</menu>
|
||||||
14
app/src/main/res/navigation/audio_recorder_navigation.xml
Normal file
14
app/src/main/res/navigation/audio_recorder_navigation.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/audio_recorder_navigation"
|
||||||
|
app:startDestination="@id/audioRecorderMainFragment">
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/audioRecorderMainFragment"
|
||||||
|
android:name="com.isolaatti.audio.recorder.ui.AudioRecorderMainFragment"
|
||||||
|
android:label="AudioRecorderMainFragment" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/audioDraftsFragment"
|
||||||
|
android:name="com.isolaatti.audio.recorder.ui.AudioDraftsFragment"
|
||||||
|
android:label="AudioDraftsFragment" />
|
||||||
|
</navigation>
|
||||||
@ -7,12 +7,28 @@
|
|||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/feedFragment"
|
android:id="@+id/feedFragment"
|
||||||
android:name="com.isolaatti.feed.ui.FeedFragment"
|
android:name="com.isolaatti.home.FeedFragment"
|
||||||
android:label="fragment_feed"
|
android:label="fragment_feed"
|
||||||
tools:layout="@layout/fragment_feed" />
|
tools:layout="@layout/fragment_feed" >
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_feedFragment_to_profileActivity"
|
||||||
|
app:destination="@id/profileActivity" />
|
||||||
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/notificationsFragment"
|
android:id="@+id/notificationsFragment"
|
||||||
android:name="com.isolaatti.home.notifications.ui.NotificationsFragment"
|
android:name="com.isolaatti.home.notifications.ui.NotificationsFragment"
|
||||||
android:label="fragment_notifications"
|
android:label="fragment_notifications"
|
||||||
tools:layout="@layout/fragment_notifications" />
|
tools:layout="@layout/fragment_notifications" >
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_notificationsFragment_to_profileActivity"
|
||||||
|
app:destination="@id/profileActivity" />
|
||||||
|
</fragment>
|
||||||
|
<activity
|
||||||
|
android:id="@+id/profileActivity"
|
||||||
|
android:name="com.isolaatti.profile.ui.ProfileActivity"
|
||||||
|
android:label="ProfileActivity" />
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/searchFragment"
|
||||||
|
android:name="com.isolaatti.home.search.presentation.SearchFragment"
|
||||||
|
android:label="@string/search" />
|
||||||
</navigation>
|
</navigation>
|
||||||
@ -18,9 +18,27 @@
|
|||||||
<string name="report">Report</string>
|
<string name="report">Report</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
|
|
||||||
|
<!-- Common -->
|
||||||
|
<string name="accept">Accept</string>
|
||||||
|
<string name="no">No</string>
|
||||||
|
|
||||||
|
<!-- error messages-->
|
||||||
|
<string name="need_reauth_message">Your session is not valid, please enter your credentials.</string>
|
||||||
|
<string name="network_error">Network error occurred, it maybe is on our side. Please try again later</string>
|
||||||
|
<string name="server_error">An error on our side has occurred.</string>
|
||||||
|
|
||||||
<!--Post options -->
|
<!--Post options -->
|
||||||
<string name="post_options_title">Discussion options</string>
|
<string name="post_options_title">Discussion options</string>
|
||||||
<string name="home">Home</string>
|
<string name="home">Home</string>
|
||||||
<string name="notifications">Notifications</string>
|
<string name="notifications">Notifications</string>
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
|
|
||||||
|
<!-- Audio recorder -->
|
||||||
|
<string name="audio_recorder">Record audio</string>
|
||||||
|
<string name="account_not_found">Could not find any account that matches %s</string>
|
||||||
|
<string name="dismiss">Dismiss</string>
|
||||||
|
<string name="wrong_password">Could not sign in, password does not match</string>
|
||||||
|
<string name="new_post">New</string>
|
||||||
|
<string name="discussion">Discussion</string>
|
||||||
|
<string name="add_audio">New audio</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,7 +1,13 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
buildscript {
|
||||||
|
dependencies {
|
||||||
|
classpath "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.0.2' apply false
|
id 'com.android.application' version '8.0.2' apply false
|
||||||
id 'com.android.library' version '8.0.2' apply false
|
id 'com.android.library' version '8.0.2' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
|
id 'org.jetbrains.kotlin.android' version '1.8.0' apply false
|
||||||
id 'com.google.dagger.hilt.android' version '2.44' apply false
|
id 'com.google.dagger.hilt.android' version '2.44' apply false
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user