Compare commits

...

No commits in common. "main" and "lab_5" have entirely different histories.
main ... lab_5

141 changed files with 8181 additions and 54 deletions

67
.gitignore vendored
View File

@ -1,52 +1,15 @@
# ---> Kotlin
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# ---> Java
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="C:\Users\ankav\.android\avd\Pixel_6_API_31_2.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-12-26T09:35:26.870656500Z" />
</component>
</project>

20
.idea/gradle.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="corretto-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,41 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.20" />
</component>
</project>

6
.idea/misc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="corretto-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,2 +0,0 @@
# PMULabs_Zhimol

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

107
app/build.gradle.kts Normal file
View File

@ -0,0 +1,107 @@
plugins {
id("com.android.application")
id("com.google.devtools.ksp")
id("org.jetbrains.kotlin.android")
id("org.jetbrains.kotlin.plugin.serialization")
}
android {
namespace = "com.example.pmulabs"
compileSdk = 34
defaultConfig {
applicationId = "com.example.pmulabs"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
viewBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.5"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
kotlin {
jvmToolchain(11)
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.2.0-alpha08")
implementation("androidx.compose.ui:ui-tooling-preview-android:1.5.2")
implementation("androidx.leanback:leanback:1.0.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.navigation:navigation-compose:2.6.0")
implementation("androidx.navigation:navigation-compose:2.4.0-alpha10")
val room_version = "2.5.2"
implementation("androidx.room:room-runtime:$room_version")
annotationProcessor("androidx.room:room-compiler:$room_version")
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
implementation("androidx.room:room-paging:$room_version")
val paging_version = "3.1.1"
implementation ("androidx.paging:paging-runtime:$paging_version")
// alternatively - without Android dependencies for tests
testImplementation ("androidx.paging:paging-common:$paging_version")
// optional - RxJava2 support
implementation ("androidx.paging:paging-rxjava2:$paging_version")
// optional - RxJava3 support
implementation ("androidx.paging:paging-rxjava3:$paging_version")
// optional - Guava ListenableFuture support
implementation ("androidx.paging:paging-guava:$paging_version")
// optional - Jetpack Compose integration
implementation ("androidx.paging:paging-compose:1.0.0-alpha18")
// retrofit
val retrofitVersion = "2.9.0"
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("androidx.paging:paging-compose:3.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,24 @@
package com.example.pmulabs
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.pmulabs", appContext.packageName)
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PMULabs"
android:name=".NewsPortalApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.PMULabs">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,26 @@
package com.example.pmulabs
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import com.example.pmulabs.graphs.RootNavigationGraph
import com.example.pmulabs.ui.theme.PMULabsTheme
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.SearchViewModel
class MainActivity : ComponentActivity() {
private val searchViewModel: SearchViewModel by viewModels()
private val currentUserViewModel: CurrentUserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PMULabsTheme {
RootNavigationGraph(navController = rememberNavController(),searchViewModel=searchViewModel, currentUserViewModel = viewModel(factory = AppViewModelProvider.Factory))
}
}
}
}

View File

@ -0,0 +1,14 @@
package com.example.pmulabs
import android.app.Application
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.AppDataContainer
class NewsPortalApplication : Application() {
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
}

View File

@ -0,0 +1,165 @@
package com.example.pmulabs.api
import com.example.pmulabs.api.model.ArticleRemote
import com.example.pmulabs.api.model.CommentRemote
import com.example.pmulabs.api.model.ReportRemote
import com.example.pmulabs.api.model.TagRemote
import com.example.pmulabs.api.model.UserRemote
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
interface NewsPortalService {
@GET("report")
suspend fun getReport(
@Query("startDate") startDate: Long,
@Query("endDate") endDate: Long
): List<ReportRemote>
@GET("users")
suspend fun getUsers(): List<UserRemote>
@GET("tags")
suspend fun getTags(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<TagRemote>
@GET("tags")
suspend fun getAllTags(): List<TagRemote>
@GET("articles")
suspend fun getAllArticles(): List<ArticleRemote>
@GET("comments")
suspend fun getAllComments(): List<CommentRemote>
@GET("articles")
suspend fun getArticles(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<ArticleRemote>
@GET("comments")
suspend fun getComments(
@Query("_page") page: Int,
@Query("_limit") limit: Int,
): List<CommentRemote>
@GET("comments")
suspend fun getCountComment(
@Query("articleId") articleId: Int
): List<CommentRemote>
@GET("comments")
suspend fun getUserComments(@Query("userId") userId: Int): List<CommentRemote>
@GET("articles")
suspend fun getUserArticles(@Query("userId") userId: Int): List<ArticleRemote>
@GET("tags")
suspend fun getUserTags(@Query("userId") userId: Int): List<TagRemote>
@GET("users/{id}")
suspend fun getUser(
@Path("id") id: Int,
): UserRemote
@POST("users")
suspend fun createUser(
@Body user: UserRemote,
): UserRemote
@PUT("users/{id}")
suspend fun updateUser(
@Path("id") id: Int,
@Body user: UserRemote,
): UserRemote
@DELETE("users/{id}")
suspend fun deleteUser(
@Path("id") id: Int,
): UserRemote
@GET("tags")
suspend fun getTagByName(
@Query("title") title: String,
): List<TagRemote>
@GET("tags/{id}")
suspend fun getTag(
@Path("id") id: Int,
): TagRemote
@POST("tags")
suspend fun createTag(
@Body tag: TagRemote,
): TagRemote
@PUT("tags/{id}")
suspend fun updateTag(
@Path("id") id: Int,
@Body tag: TagRemote,
): TagRemote
@DELETE("tags/{id}")
suspend fun deleteTag(
@Path("id") id: Int,
): TagRemote
@GET("articles/{id}")
suspend fun getArticle(
@Path("id") id: Int,
): ArticleRemote
@POST("articles")
suspend fun createArticle(
@Body article: ArticleRemote,
): ArticleRemote
@PUT("articles/{id}")
suspend fun updateArticle(
@Path("id") id: Int,
@Body article: ArticleRemote,
): ArticleRemote
@DELETE("articles/{id}")
suspend fun deleteArticle(
@Path("id") id: Int,
): ArticleRemote
@GET("comments/{id}")
suspend fun getComment(
@Path("id") id: Int,
): CommentRemote
@POST("comments")
suspend fun createComment(
@Body comment: CommentRemote,
): CommentRemote
@PUT("comments/{id}")
suspend fun updateComment(
@Path("id") id: Int,
@Body comment: CommentRemote,
): CommentRemote
@DELETE("comments/{id}")
suspend fun deleteComment(
@Path("id") id: Int,
): CommentRemote
companion object {
private const val BASE_URL = "http://192.168.0.103:8079/"
@Volatile
private var INSTANCE: NewsPortalService? = null
fun getInstance(): NewsPortalService {
return INSTANCE ?: synchronized(this) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
val client = OkHttpClient.Builder()
.addInterceptor(logger)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
.create(NewsPortalService::class.java)
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,112 @@
package com.example.pmulabs.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toArticle
import com.example.pmulabs.api.repository.RestTagRepository
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class ArticleRemoteMediator(
private val service: NewsPortalService,
private val dbArticleRepository: OfflineArticleRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val tagRestRepository: RestTagRepository,
private val database: NewsPortalDatabase
) : RemoteMediator<Int, Article>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Article>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val articles = service.getArticles(page, state.config.pageSize).map { it.toArticle() }
val endOfPaginationReached = articles.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ARTICLE)
dbArticleRepository.clearArticles()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = articles.map {
it.id?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.ARTICLE,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
tagRestRepository.getAllTags()
dbArticleRepository.insertArticles(articles)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Article>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { art ->
art.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ARTICLE) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Article>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { art ->
art.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.ARTICLE) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Article>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { artid ->
dbRemoteKeyRepository.getAllRemoteKeys(artid, RemoteKeyType.ARTICLE)
}
}
}
}

View File

@ -0,0 +1,111 @@
package com.example.pmulabs.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toComment
import com.example.pmulabs.api.repository.RestArticleRepository
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class CommentRemoteMediator(
private val service: NewsPortalService,
private val dbCommentRepository: OfflineCommentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val articleRestRepository: RestArticleRepository,
private val database: NewsPortalDatabase
) : RemoteMediator<Int, Comment>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Comment>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val comms = service.getComments(page, state.config.pageSize).map { it.toComment() }
val endOfPaginationReached = comms.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.COMMENT)
dbCommentRepository.clearComments()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = comms.map {
it.id?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.COMMENT,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
articleRestRepository.getAllArticles()
dbCommentRepository.insertComments(comms)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Comment>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { comm ->
comm.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.COMMENT) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Comment>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { comm ->
comm.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.COMMENT) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Comment>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { commid ->
dbRemoteKeyRepository.getAllRemoteKeys(commid, RemoteKeyType.COMMENT)
}
}
}
}

View File

@ -0,0 +1,109 @@
package com.example.pmulabs.api.mediator
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.toTag
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class TagRemoteMediator(
private val service: NewsPortalService,
private val dbTagRepository: OfflineTagRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: NewsPortalDatabase
) : RemoteMediator<Int, Tag>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Tag>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val tags = service.getTags(page, state.config.pageSize).map { it.toTag() }
val endOfPaginationReached = tags.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.TAG)
dbTagRepository.clearTags()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = tags.map {
it.id?.let { it1 ->
RemoteKeys(
entityId = it1,
type = RemoteKeyType.TAG,
prevKey = prevKey,
nextKey = nextKey
)
}
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbTagRepository.insertTags(tags)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Tag>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { tag ->
tag.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.TAG) }
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Tag>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { tag ->
tag.id?.let { dbRemoteKeyRepository.getAllRemoteKeys(it, RemoteKeyType.TAG) }
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Tag>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { tagid ->
dbRemoteKeyRepository.getAllRemoteKeys(tagid, RemoteKeyType.TAG)
}
}
}
}

View File

@ -0,0 +1,32 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.Article
import kotlinx.serialization.Serializable
@Serializable
data class ArticleRemote(
val id: Int?=0,
var title: String="",
var text: String="",
val publishDate: Long=0,
val userId: Int=0,
var tagId: Int=0
)
fun ArticleRemote.toArticle(): Article = Article(
id,
title,
text,
publishDate,
userId,
tagId
)
fun Article.toArticleRemote(): ArticleRemote = ArticleRemote(
id,
title,
text,
publishDate,
userId,
tagId
)

View File

@ -0,0 +1,26 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.Comment
import kotlinx.serialization.Serializable
@Serializable
data class CommentRemote(
val id: Int?=0,
var text: String="",
val userId: Int=0,
val articleId: Int=0
)
fun CommentRemote.toComment(): Comment = Comment(
id,
text,
userId,
articleId
)
fun Comment.toCommentRemote(): CommentRemote = CommentRemote(
id,
text,
userId,
articleId
)

View File

@ -0,0 +1,12 @@
package com.example.pmulabs.api.model
import kotlinx.serialization.Serializable
@Serializable
data class ReportRemote(
val title: String = "",
val publishDate: Long = 0,
val tagName: String = "",
val userName: String = "",
val comments: Int = 0
)

View File

@ -0,0 +1,23 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.Tag
import kotlinx.serialization.Serializable
@Serializable
data class TagRemote(
var title: String="",
val id: Int?=0,
val userId: Int=0
)
fun TagRemote.toTag(): Tag = Tag(
title,
id,
userId
)
fun Tag.toTagRemote(): TagRemote = TagRemote(
title,
id,
userId
)

View File

@ -0,0 +1,29 @@
package com.example.pmulabs.api.model
import com.example.pmulabs.room.models.User
import kotlinx.serialization.Serializable
@Serializable
data class UserRemote(
val id: Int?=0,
var nickname: String="",
var email: String="",
var password: String="",
val role: String=""
)
fun UserRemote.toUser(): User = User(
id,
nickname,
email,
password,
role
)
fun User.toUserRemote(): UserRemote = UserRemote(
id,
nickname,
email,
password,
role
)

View File

@ -0,0 +1,79 @@
package com.example.pmulabs.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.mediator.ArticleRemoteMediator
import com.example.pmulabs.api.model.toArticle
import com.example.pmulabs.api.model.toArticleRemote
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestArticleRepository(
private val service: NewsPortalService,
private val dbArticleRepository: OfflineArticleRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val dbTagRepository: RestTagRepository,
private val database: NewsPortalDatabase
) : ArticleRepository {
override suspend fun insertArticle(article: Article) {
service.createArticle(article.toArticleRemote()).toArticle()
}
override suspend fun updateArticle(article: Article) {
article.id?.let { service.updateArticle(it, article.toArticleRemote()).toArticle() }
}
override suspend fun deleteArticle(article: Article) {
article.id?.let { service.deleteArticle(it).toArticle() }
}
override suspend fun getAllArticles(): List<Article> {
dbTagRepository.getAllTags()
val existArticles = dbArticleRepository.getAllArticles().associateBy { it.id }.toMutableMap()
service.getAllArticles()
.map { it.toArticle() }
.forEach { art ->
val existArt = existArticles[art.id]
if (existArt == null) {
dbArticleRepository.insertArticle(art)
} else if (existArt != art) {
dbArticleRepository.updateArticle(art)
}
existArticles[art.id] = art
}
return existArticles.map { it.value }.sortedBy { it.id }
}
override suspend fun getArticleById(idArticle: Int): Article? =
idArticle?.let { service.getArticle(it).toArticle() }
override fun getArticles(): Flow<PagingData<Article>> {
val pagingSourceFactory = { dbArticleRepository.getAllArticlesPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = ArticleRemoteMediator(
service,
dbArticleRepository,
dbRemoteKeyRepository,
dbTagRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,85 @@
package com.example.pmulabs.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.mediator.CommentRemoteMediator
import com.example.pmulabs.api.model.toComment
import com.example.pmulabs.api.model.toCommentRemote
import com.example.pmulabs.api.model.toUser
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import kotlinx.coroutines.flow.Flow
class RestCommentRepository(
private val service: NewsPortalService,
private val dbCommentRepository: OfflineCommentRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val dbArticleRepository: RestArticleRepository,
private val database: NewsPortalDatabase
) : CommentRepository {
override suspend fun insertComment(comment: Comment) {
service.createComment(comment.toCommentRemote()).toComment()
}
override suspend fun updateComment(comment: Comment) {
comment.id?.let { service.updateComment(it, comment.toCommentRemote()).toComment() }
}
override suspend fun deleteComment(comment: Comment) {
comment.id?.let { service.deleteComment(it).toComment() }
}
override suspend fun getAllComments(): List<Comment> {
val existComms = dbCommentRepository.getAllComments().associateBy { it.id }.toMutableMap()
service.getAllComments()
.map { it.toComment() }
.forEach { comm ->
val existComm = existComms[comm.id]
if (existComm == null) {
dbCommentRepository.insertComment(comm)
} else if (existComm != comm) {
dbCommentRepository.updateComment(comm)
}
existComms[comm.id] = comm
}
return existComms.map { it.value }.sortedBy { it.id }
}
override suspend fun getCountComment(idArticle: Int?): Int? {
return idArticle?.let { service.getCountComment(it).size }
}
override suspend fun getUser(commId : Int) : User? =
commId?.let { service.getUser(it).toUser() }
override fun getComments(): Flow<PagingData<Comment>> {
val pagingSourceFactory = { dbCommentRepository.getAllCommentsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = CommentRemoteMediator(
service,
dbCommentRepository,
dbRemoteKeyRepository,
dbArticleRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,80 @@
package com.example.pmulabs.api.repository
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.mediator.TagRemoteMediator
import com.example.pmulabs.api.model.toTag
import com.example.pmulabs.api.model.toTagRemote
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import com.example.pmulabs.room.repository.TagRepository
import kotlinx.coroutines.flow.Flow
class RestTagRepository(
private val service: NewsPortalService,
private val dbTagRepository: OfflineTagRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: NewsPortalDatabase
) : TagRepository {
override suspend fun insertTag(tag: Tag) {
service.createTag(tag.toTagRemote()).toTag()
}
override suspend fun updateTag(tag: Tag) {
tag.id?.let { service.updateTag(it, tag.toTagRemote()).toTag() }
}
override suspend fun deleteTag(tag: Tag) {
tag.id?.let { service.deleteTag(it).toTag() }
}
override suspend fun getAllTags(): List<Tag> {
val existTags = dbTagRepository.getAllTags().associateBy { it.id }.toMutableMap()
service.getAllTags()
.map { it.toTag() }
.forEach { tag ->
val existTag = existTags[tag.id]
if (existTag == null) {
dbTagRepository.insertTag(tag)
} else if (existTag != tag) {
dbTagRepository.updateTag(tag)
}
existTags[tag.id] = tag
}
return existTags.map { it.value }.sortedBy { it.id }
}
override suspend fun getTagById(idTag: Int): Tag? =
idTag?.let { service.getTag(it).toTag() }
override suspend fun getTagByName(nameTag: String): Tag? =
nameTag?.let { service.getTagByName(it)[0].toTag() }
override fun getTags(): Flow<PagingData<Tag>> {
val pagingSourceFactory = { dbTagRepository.getAllTagsPagingSource() }
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = TagRemoteMediator(
service,
dbTagRepository,
dbRemoteKeyRepository,
database,
),
pagingSourceFactory = pagingSourceFactory
).flow
}
}

View File

@ -0,0 +1,125 @@
package com.example.pmulabs.api.repository
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.model.ReportRemote
import com.example.pmulabs.api.model.toArticle
import com.example.pmulabs.api.model.toComment
import com.example.pmulabs.api.model.toTag
import com.example.pmulabs.api.model.toUser
import com.example.pmulabs.api.model.toUserRemote
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import com.example.pmulabs.room.repository.OfflineUserRepository
import com.example.pmulabs.room.repository.UserRepository
class RestUserRepository(
private val service: NewsPortalService,
private val dbUserRepository: OfflineUserRepository,
private val dbCommentRepository: OfflineCommentRepository,
private val dbTagRepository: OfflineTagRepository,
private val dbArticleRepository: OfflineArticleRepository
) : UserRepository {
override suspend fun insertUser(user: User) {
service.createUser(user.toUserRemote()).toUser()
}
override suspend fun getReport(startDate: Long, endDate: Long):List<ReportRemote>
{
return service.getReport(startDate,endDate)
}
override suspend fun updateUser(user: User) {
user.id?.let { service.updateUser(it, user.toUserRemote()).toUser() }
}
override suspend fun deleteUser(user: User) {
user.id?.let { service.deleteUser(it).toUser() }
}
override suspend fun getAllUsers(): List<User> {
val existUsers = dbUserRepository.getAllUsers().associateBy { it.id }.toMutableMap()
service.getUsers()
.map { it.toUser() }
.forEach { user ->
val existUser = existUsers[user.id]
if (existUser == null) {
dbUserRepository.insertUser(user)
} else if (existUser != user) {
dbUserRepository.updateUser(user)
}
existUsers[user.id] = user
}
return existUsers.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserById(idUser: Int?): User? =
idUser?.let { service.getUser(it).toUser() }
override suspend fun getUserComms(idUser: Int): List<Comment> {
val existComments = dbUserRepository.getUserComms(idUser).associateBy { it.id }.toMutableMap()
service.getUserComments(idUser)
.map { it.toComment() }
.forEach { comm ->
if(comm.userId==idUser) {
val existComm = existComments[comm.id]
if (existComm == null) {
dbCommentRepository.insertComment(comm)
} else if (existComm != comm) {
dbCommentRepository.updateComment(comm)
}
existComments[comm.id] = comm
}
}
return existComments.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserArticles(idUser: Int): List<Article> {
val existArticles = dbUserRepository.getUserArticles(idUser).associateBy { it.id }.toMutableMap()
service.getUserArticles(idUser)
.map { it.toArticle() }
.forEach { art ->
if(art.userId==idUser) {
val existArt = existArticles[art.id]
if (existArt == null) {
dbArticleRepository.insertArticle(art)
} else if (existArt != art) {
dbArticleRepository.updateArticle(art)
}
existArticles[art.id] = art
}
}
return existArticles.map { it.value }.sortedBy { it.id }
}
override suspend fun getUserTags(idUser: Int): List<Tag> {
val existTags = dbUserRepository.getUserTags(idUser).associateBy { it.id }.toMutableMap()
service.getUserTags(idUser)
.map { it.toTag() }
.forEach { tag ->
if(tag.userId==idUser) {
val existTag = existTags[tag.id]
if (existTag == null) {
dbTagRepository.insertTag(tag)
} else if (existTag != tag) {
dbTagRepository.updateTag(tag)
}
existTags[tag.id] = tag
}
}
return existTags.map { it.value }.sortedBy { it.id }
}
}

View File

@ -0,0 +1,131 @@
package com.example.pmulabs.basecomponents.navigate
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
@Composable
fun BottomBar(navController: NavHostController){
val screens = listOf(
BottomBarScreen.Profile,
BottomBarScreen.Categories,
BottomBarScreen.Main,
BottomBarScreen.Cooperation,
BottomBarScreen.Info
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination=navBackStackEntry?.destination
val bottomBarDestination=screens.any{ it.route==currentDestination?.route }
Row(
modifier = Modifier
.padding(start = 10.dp, end = 10.dp, top = 8.dp, bottom = 8.dp)
.background(Color.Transparent),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
if (bottomBarDestination) {
NavigationBar(
containerColor = Color(0xFFDBDBF1),
modifier = Modifier
.height(40.dp)
.clip(shape = RoundedCornerShape(20.dp))
) {
screens.forEach { screen ->
AddItem(
screen = screen,
currentDestination = currentDestination,
navController = navController
)
}
}
}
}
}
@Composable
fun RowScope.AddItem(
screen: BottomBarScreen,
currentDestination: NavDestination?,
navController: NavController
){
val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
val background = if (selected) Color(0xff423a99) else Color.Transparent
val contentColor =
if (selected) Color.White else Color(0xff423a99)
Box(
modifier = Modifier
.height(40.dp)
.clip(CircleShape)
.background(background)
.clickable(onClick = {
navController.navigate(screen.route) {
popUpTo(navController.graph.findStartDestination().id)
launchSingleTop = true
}
})
) {
Row(
modifier = Modifier
.padding(start = 10.dp, end = 17.dp, top = 8.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Icon(
painter = painterResource(id = if (selected) screen.iconFocused else screen.icon),
contentDescription = "icon",
tint = Color.Unspecified
)
AnimatedVisibility(visible = selected) {
Text(
text = screen.title,
color = contentColor
)
}
}
}
/*NavigationBarItem(
label = {
Text(text=screen.title, color = Color(0xff423a99))
},
icon = {
Icon(
painter = painterResource(id = screen.icon),
tint = Color.Unspecified,
contentDescription = "navigation icon")
},
selected = selected,
onClick = {
navController.navigate(screen.route)
}
)*/
}

View File

@ -0,0 +1,93 @@
package com.example.pmulabs.basecomponents.navigate
import com.example.pmulabs.R
const val SEARCH_ARGUMENT_TEXT="text"
const val CALENDAR_ARGUMENT_DATE="date"
const val SEARCHBYTAG_ARGUMENT_KEY="id"
const val ARTICLE_ARGUMENT_KEY="id"
sealed class BottomBarScreen(
val route: String,
val title: String,
val icon: Int,
val iconFocused: Int
){
object Profile: BottomBarScreen(
route = "PROFILE",
title="Me",
icon= R.drawable.ic_bottom_profile,
iconFocused=R.drawable.ic_bottom_profile_focused
)
object Categories: BottomBarScreen(
route = "CATEGORIES",
title="Tags",
icon= R.drawable.ic_bottom_categories,
iconFocused=R.drawable.ic_bottom_categories_focused
)
object Main: BottomBarScreen(
route = "MAIN",
title="Main",
icon= R.drawable.ic_bottom_main,
iconFocused=R.drawable.ic_bottom_main_focused
)
object ArticlePage: BottomBarScreen(
route = "ARTICLEPAGE/{$ARTICLE_ARGUMENT_KEY}",
title="Article",
icon= R.drawable.ic_bottom_main,
iconFocused=R.drawable.ic_bottom_main_focused
){
fun passId(id: String): String{
return "ARTICLEPAGE/$id"
}
}
object Search: BottomBarScreen(
route = "SEARCH/{$SEARCH_ARGUMENT_TEXT}",
title="Search",
icon= R.drawable.ic_bottom_main,
iconFocused=R.drawable.ic_bottom_main_focused
){
fun passText(text: String): String{
return "SEARCH/$text"
}
}
object Calendar: BottomBarScreen(
route = "CALENDAR/{$CALENDAR_ARGUMENT_DATE}",
title="Calendar",
icon= R.drawable.ic_bottom_main,
iconFocused=R.drawable.ic_bottom_main_focused
){
fun passDate(date: String): String{
return "CALENDAR/$date"
}
}
object SearchByTag: BottomBarScreen(
route = "SEARCHBYTAG/{$SEARCHBYTAG_ARGUMENT_KEY}",
title="SEARCHBYTAG",
icon= R.drawable.ic_bottom_main,
iconFocused=R.drawable.ic_bottom_main_focused
){
fun passId(id: String): String{
return "SEARCHBYTAG/$id"
}
}
object Cooperation: BottomBarScreen(
route = "COOPERATION",
title="Coop",
icon= R.drawable.ic_bottom_cooperation,
iconFocused=R.drawable.ic_bottom_cooperation_focused
)
object Info: BottomBarScreen(
route = "INFO",
title="Info",
icon= R.drawable.ic_bottom_info,
iconFocused=R.drawable.ic_bottom_info_focused
)
object Report: BottomBarScreen(
route = "REPORT",
title="Report",
icon= R.drawable.ic_bottom_info,
iconFocused=R.drawable.ic_bottom_info_focused
)
}

View File

@ -0,0 +1,155 @@
package com.example.pmulabs.basecomponents.navigate
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CalendarToday
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.pmulabs.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBar(onSearchClicked:() -> Unit, onCalendarClicked: () -> Unit) {
TopAppBar(
colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color(0xff423a99) ),
title = {
Image(
painter = painterResource(id = R.drawable.ellipse7),
contentDescription = "Ellipse 7",
modifier = Modifier
.requiredHeight(height = 62.dp)
)
Image(
painter = painterResource(id = R.drawable.ixbtcom_colored_logo1),
contentDescription = "IXBT.com_colored_logo 1",
modifier = Modifier
.offset(
x = 4.dp,
y = 0.dp
)
.requiredHeight(height = 60.dp)
)
},
actions = {
IconButton(onClick = { onSearchClicked() }) {
Icon(
imageVector = Icons.Filled.Search,
contentDescription = "Search Icon",
tint = Color.White
)
}
IconButton(onClick = { onCalendarClicked() }) {
Icon(
imageVector = Icons.Filled.CalendarToday,
contentDescription = "Calendar Icon",
tint = Color.White
)
}
})
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchAppBar(
text: String,
onTextChange: (String) -> Unit,
onCloseClicked: () -> Unit,
onSearchClicked: (String) -> Unit
){
Surface(
modifier = Modifier
.fillMaxWidth()
.height(62.dp),
color = Color(0xff423a99)
) {
TextField(
colors=TextFieldDefaults.textFieldColors(
Color.White, containerColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth(),
value=text,
onValueChange={
onTextChange(it)
},
placeholder = {
Text(
text ="Search...",
color = Color.White
)
},
textStyle = TextStyle(
fontSize = MaterialTheme.typography.titleMedium.fontSize
),
singleLine = true,
leadingIcon = {
IconButton(
onClick = { }
) {
Icon(imageVector = Icons.Default.Search,
contentDescription = "Search Icon",
tint = Color.White)
}
},
trailingIcon = {
IconButton(
onClick = {
if(text.isNotEmpty()){
onTextChange("")
}else{
onCloseClicked()
}
}
) {
Icon(imageVector = Icons.Default.Close,
contentDescription = "Close Icon",
tint = Color.White)
}
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
onSearchClicked(text)
}
)
)
}
}
@Composable
@Preview
fun TopBarPreview(){
TopBar(onSearchClicked = {},onCalendarClicked = {})
}
@Composable
@Preview
fun SearchBarPreview(){
SearchAppBar(text = "вапвапв", onSearchClicked = {}, onCloseClicked = {}, onTextChange = {})
}

View File

@ -0,0 +1,47 @@
package com.example.pmulabs.designElem.elem
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun BackButton(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.requiredWidth(width = 80.dp)
.requiredHeight(height = 30.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 80.dp)
.requiredHeight(height = 30.dp)
.clip(shape = RoundedCornerShape(6.dp))
.background(color = Color(0xff423a99)))
Text(
text = "Back",
color = Color.White,
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 20.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 17.dp,
y = 3.dp))
}
}

View File

@ -0,0 +1,63 @@
package com.example.pmulabs.designElem.elem
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EmailTextField(email: String, onEmailChange: (String) -> Unit) {
TextField(
value = email,
onValueChange = {
onEmailChange(it)
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White
),
modifier = Modifier
.requiredWidth(width = 269.dp)
.requiredHeight(height = 53.dp)
.shadow(shape = RoundedCornerShape(40.dp), elevation = 5.dp)
.clip(shape = RoundedCornerShape(40.dp)),
label = { Text("Email") },
placeholder = { Text("Email") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
singleLine = true,
)
}
fun isValidEmail(email: String): Boolean {
val emailRegex = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
return email.matches(emailRegex)
}
@Composable
fun ValidateEmail(email: String, onEmailChange: (String) -> Unit) {
Column (modifier = Modifier.padding(16.dp)) {
EmailTextField(email = email, onEmailChange = { onEmailChange(it)})
if (email.isNotEmpty()) {
if (isValidEmail(email)) {
Text(text = "Email is valid", color = Color(0xFF5D925E))
} else {
Text(text = "Email is not valid", color = Color(0xFF94474D))
}
}
}
}

View File

@ -0,0 +1,434 @@
package com.example.pmulabs.designElem.items
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toSize
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.R
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticleItemViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
@SuppressLint("UnrememberedMutableState", "CoroutineCreationDuringComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ArticleItem(navController: NavController,article: Article, modifier: Modifier = Modifier, onArticleClick: () -> Unit, articleItemViewModel: ArticleItemViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val coroutineScope = rememberCoroutineScope()
articleItemViewModel.setTagList()
//список тэгов
val tags = mutableStateOf<List<Tag>>(articleItemViewModel.tagList)
//количество комментариев для статьи
var countComm = remember{mutableStateOf<Int?>(0)}
coroutineScope.launch {
countComm.value = articleItemViewModel.getCount(article.id)
}
//текущий пользователь
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
//формат даты
val formatter = SimpleDateFormat("dd.MM.YY")
val publishDate=formatter.format(article.publishDate)
//список названий тэгов
var tagName=""
var tagsNames= remember { mutableStateListOf<String>() }
tags.value.forEach{teg ->
if(article.tagId == teg.id){
tagName=teg.title
}
if(!tagsNames.contains(teg.title)) {
tagsNames.add(teg.title)
}
}
//открыто ли диалоговое окно
var openDialog by remember { mutableStateOf(false) }
//переменные article для update
var text by remember { mutableStateOf(article.text) }
var tagId by remember { mutableStateOf(article.tagId) }
var title by remember { mutableStateOf(article.title) }
//переменные для dropdown
var expanded by remember { mutableStateOf(false) }
val suggestions = tagsNames
var selectedText by remember { mutableStateOf(tagName) }
var textfieldSize by remember { mutableStateOf(Size.Zero)}
val icon = if (expanded)
Icons.Filled.ArrowDropUp
else
Icons.Filled.ArrowDropDown
//диалоговое окно
if (openDialog) {
AlertDialog(
onDismissRequest = {
openDialog = false
text=article.text
selectedText=tagName
},
content = {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(16.dp),
) {
Text(
text = "Edit your article",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 20.sp,
),
)
Box() {
OutlinedTextField(
value = selectedText,
onValueChange = { selectedText = it },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.onGloballyPositioned { coordinates ->
textfieldSize = coordinates.size.toSize()
},
label = {Text("Select Tag")},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
trailingIcon = {
Icon(icon,"List of tags",
Modifier.clickable { expanded = !expanded })
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current){textfieldSize.width.toDp()})
) {
suggestions.forEach { label ->
DropdownMenuItem(
text= {Text(text=label)},
onClick = { selectedText = label
coroutineScope.launch {
tagId= articleItemViewModel.getTagByName(selectedText)?.id!!
}
}
)
}
}
}
OutlinedTextField(
value = title,
onValueChange = {
title=it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Article Title") },
placeholder = { Text("Write title of article...") },
minLines = 3,
maxLines = 3,
)
OutlinedTextField(
value = text,
onValueChange = {
text=it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Article Text") },
placeholder = { Text("Write text of article...") },
minLines = 3,
maxLines = 3,
)
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
openDialog = false
article.text = text
article.title = title
tagName = selectedText
Log.d("Tag", tagId.toString())
if (tagId != null) {
article.tagId = tagId
}
coroutineScope.launch {
val upResult = async {
articleItemViewModel.updateArticle(article)
}
upResult.await()
}
},
modifier = Modifier
.padding(start=100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
Text(text = "Edit", fontSize = 20.sp)
}
}
}
},
)
}
Card(
colors = CardDefaults.outlinedCardColors(
containerColor = Color.White,
contentColor = Color(0xff423a99)
),
border = BorderStroke(3.dp, Color(0xffdbdbf1)),
modifier = modifier
.clip(shape = RoundedCornerShape(5.dp))
.clickable{onArticleClick()}
) {
Box(
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 199.dp)
) {
Card(
colors = CardDefaults.outlinedCardColors(
containerColor = Color.White
),
border = BorderStroke(3.dp, Color(0xffdbdbf1)),
content = {},
modifier = Modifier
.requiredWidth(width = 393.dp)
.requiredHeight(height = 199.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)))
LazyRow(
modifier = Modifier
.requiredWidth(width = 387.dp)
.requiredHeight(height = 199.dp)
) {
item {
Column(
modifier = Modifier
//.align(alignment = Alignment.TopStart)
.offset(x = 38.dp,
y = 8.dp)
.requiredWidth(width = 351.dp)
.requiredHeight(height = 177.dp)
) {
LazyColumn(
modifier = Modifier
.requiredWidth(width = 351.dp)
.requiredHeight(height = 150.dp)
) {
item {
LazyRow(
modifier = Modifier
.requiredWidth(width = 351.dp)
.requiredHeight(height = 20.dp)
) {
item {
Text(
text = "${publishDate}",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 17.sp))
}
item {
Text(
text = "#${tagName}",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 17.sp),
modifier = Modifier
//.align(alignment = Alignment.TopStart)
.offset(x = 12.dp,
y = 0.dp))
}
item{
if(getUser?.id==article.userId) {
Icon(
modifier=Modifier
.align(Alignment.End)
.offset(x = 20.dp,
y = 0.dp)
.requiredHeight(30.dp)
.clickable {
openDialog=true
},
imageVector = Icons.Filled.Edit,
contentDescription = "Update Icon",
tint = Color(0xff423a99)
)
Icon(
modifier=Modifier
.align(Alignment.End)
.offset(x = 20.dp,
y = 0.dp)
.requiredHeight(30.dp)
.clickable {
coroutineScope.launch {
articleItemViewModel.deleteArticle(article)
}
navController.navigate(BottomBarScreen.Main.route)
},
imageVector = Icons.Filled.Close,
contentDescription = "Delete Icon",
tint = Color(0xff423a99)
)
}
}
}
}
item {
Text(
text = "${article.title}",
maxLines=5,
color = Color(0xff423a99),
style = TextStyle(
fontSize = 18.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
//.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 15.dp)
.requiredWidth(width = 321.dp)
.requiredHeight(height = 110.dp))
}
}
LazyRow(
modifier = Modifier
//.align(alignment = Alignment.TopStart)
.offset(x = 3.dp,
y = 5.dp)
.requiredWidth(width = 80.dp)
.requiredHeight(height = 25.dp)
) {
item {
Row(
modifier = Modifier
.requiredWidth(width = 58.dp)
.requiredHeight(height = 22.dp)
) {
Image(
painter = painterResource(id = R.drawable.ic_comment),
contentDescription = "icon \"heart\"",
modifier = Modifier
.requiredWidth(width = 25.dp)
.requiredHeight(height = 22.dp))
Text(
text = "${countComm.value}",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 17.sp),
modifier = Modifier
//.align(alignment = Alignment.TopStart)
.offset(x = 5.dp,
y = 1.dp))
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,191 @@
package com.example.pmulabs.designElem.items
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.CommentItemViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import kotlinx.coroutines.launch
@SuppressLint("CoroutineCreationDuringComposition", "UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CommentItem(navController: NavController, modifier: Modifier = Modifier, comm : Comment, user : User, commentItemViewModel: CommentItemViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
var openDialog by remember { mutableStateOf(false) }
var text by remember { mutableStateOf(comm.text) }
val coroutineScope = rememberCoroutineScope()
if (openDialog) {
AlertDialog(
onDismissRequest = {
openDialog = false
text=comm.text
},
content = {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(16.dp),
) {
Text(
text = "Edit your comment",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 20.sp,
),
)
OutlinedTextField(
value = text,
onValueChange = {
text=it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Comment") },
placeholder = { Text("Write comment...") },
minLines = 3,
maxLines = 3,
)
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
openDialog = false
comm.text=text
coroutineScope.launch {
commentItemViewModel.updateComment(comm)
}
},
modifier = Modifier
.padding(start = 100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
Text(text = "Edit", fontSize = 20.sp)
}
}
}
},
)
}
Row(
modifier = Modifier
.padding(3.dp)
.fillMaxWidth()
.background(Color.White)
)
{
Column(
modifier= Modifier
.padding(start = 10.dp,top=10.dp)
) {
Row() {
Log.d("User",user.toString())
Text(
text = "${user.nickname}",
color = Color(0xff423a99),
style = TextStyle(
textDecoration = TextDecoration.Underline,
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
)
if(getUser?.id==comm.userId) {
Icon(
modifier= Modifier
.requiredHeight(20.dp)
.clickable {
openDialog = true
},
imageVector = Icons.Filled.Edit,
contentDescription = "Update Icon",
tint = Color(0xff423a99)
)
Icon(
modifier= Modifier
.requiredHeight(20.dp)
.clickable {
coroutineScope.launch {
commentItemViewModel.deleteComment(comm)
}
navController.navigate(BottomBarScreen.ArticlePage.passId(comm.articleId.toString()))
},
imageVector = Icons.Filled.Close,
contentDescription = "Delete Icon",
tint = Color(0xff423a99)
)
}
}
Text(
text = "${comm.text}",
color = Color(0xff423a99),
style = TextStyle(fontSize = 15.sp, fontWeight = FontWeight.Bold),
)
Spacer(modifier = Modifier.padding(10.dp))
}
}
}

View File

@ -0,0 +1,211 @@
package com.example.pmulabs.designElem.items
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.R
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.TagItemViewModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TagItem(navController: NavController,teg: Tag, modifier: Modifier = Modifier, onTagClick: () -> Unit, viewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory), tagViewModel: TagItemViewModel =viewModel(factory = AppViewModelProvider.Factory)) {
var getUser by remember { mutableStateOf(viewModel.user) }
var openDialog by remember { mutableStateOf(false) }
var text by remember { mutableStateOf(teg.title) }
val coroutineScope = rememberCoroutineScope()
if (openDialog) {
AlertDialog(
onDismissRequest = {
openDialog = false
text = teg.title
},
content = {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(16.dp),
) {
Text(
text = "Edit your tag",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 20.sp,
),
)
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Tag") },
placeholder = { Text("Write tag...") },
minLines = 3,
maxLines = 3,
)
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
openDialog = false
teg.title = text
coroutineScope.launch {
tagViewModel.updateTag(teg)
}
},
modifier = Modifier
.padding(start = 100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
Text(text = "Edit", fontSize = 20.sp)
}
}
}
},
)
}
Box(
modifier = modifier
.requiredWidth(width = 305.dp)
.requiredHeight(height = 58.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 305.dp)
.requiredHeight(height = 58.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xFFDBDBF1))
.clickable { onTagClick() }
)
Icon(
painter = painterResource(id = R.drawable.iconhashtag1),
tint = Color(0xff423a99),
contentDescription = "icon \"hashtag 1\"",
modifier = Modifier
.offset(
x = 20.dp,
y = 14.dp
)
.requiredWidth(width = 31.dp)
.requiredHeight(height = 29.dp)
)
Text(
text = "${teg.title}",
color = Color(0xff423a99),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.offset(
x = 64.dp,
y = 14.dp
)
)
if (getUser?.id == teg.userId) {
Icon(
modifier = Modifier
.requiredHeight(40.dp)
.offset(
x = 230.dp,
y = 9.dp
)
.clickable {
openDialog = true
},
imageVector = Icons.Filled.Edit,
contentDescription = "Update Icon",
tint = Color(0xff423a99)
)
Icon(
modifier = Modifier
.requiredHeight(40.dp)
.offset(
x = 260.dp,
y = 9.dp
)
.clickable {
coroutineScope.launch {
tagViewModel.deleteTag(teg)
}
navController.navigate(BottomBarScreen.Categories.route)
},
imageVector = Icons.Filled.Close,
contentDescription = "Delete Icon",
tint = Color(0xff423a99)
)
}
}
}

View File

@ -0,0 +1,60 @@
package com.example.pmulabs.designElem.statDesign
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.shape.CircleShape
@Composable
fun LeftCircles(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.requiredWidth(width = 250.dp)
.requiredHeight(height = 290.dp)
.rotate(degrees = -13.96f)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 166.4320068359375.dp,
y = 30.8746337890625.dp)
.requiredWidth(width = 122.dp)
.requiredHeight(height = 115.dp)
.clip(shape = CircleShape)
.background(color = Color(0xff423a99).copy(alpha = 0.7f)))
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = (-0.000030517578125).dp,
y = 145.57101440429688.dp)
.requiredWidth(width = 144.dp)
.requiredHeight(height = 140.dp)
.clip(shape = CircleShape)
.background(color = Color(0xff423a99).copy(alpha = 0.7f)))
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 74.10296630859375.dp,
y = 48.450347900390625.dp)
.requiredSize(size = 155.dp)
.clip(shape = CircleShape)
.background(color = Color(0xffb7b7e3).copy(alpha = 0.5f)))
}
}
@Preview(widthDp = 250, heightDp = 290)
@Composable
private fun LeftCirclesPreview() {
LeftCircles(Modifier)
}

View File

@ -0,0 +1,47 @@
package com.example.pmulabs.designElem.statDesign
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.pmulabs.R
@Composable
fun LogoMobile(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.requiredWidth(width = 250.dp)
.requiredHeight(height = 138.dp)
) {
Image(
painter = painterResource(id = R.drawable.ellipse7),
contentDescription = "Ellipse 7",
modifier = Modifier
.fillMaxWidth()
.requiredHeight(height = 115.dp)
)
Image(
painter = painterResource(id = R.drawable.ixbtcom_colored_logo1),
contentDescription = "IXBT.com_colored_logo 1",
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 0.dp)
.fillMaxWidth()
.requiredHeight(height = 113.dp))
}
}
@Preview(widthDp = 250, heightDp = 138)
@Composable
private fun LogoMobilePreview() {
LogoMobile(Modifier)
}

View File

@ -0,0 +1,59 @@
package com.example.pmulabs.designElem.statDesign
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun RightCircles(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.requiredWidth(width = 355.dp)
.requiredHeight(height = 267.dp)
.rotate(degrees = -6.33f)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 171.71197509765625.dp,
y = 17.979248046875.dp)
.requiredWidth(width = 192.dp)
.requiredHeight(height = 179.dp)
.clip(shape = CircleShape)
.background(color = Color(0xff423a99).copy(alpha = 0.7f)))
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 86.4691162109375.dp,
y = 75.193603515625.dp)
.requiredSize(size = 201.dp)
.clip(shape = CircleShape)
.background(color = Color(0xff423a99).copy(alpha = 0.7f)))
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.7720947265625.dp,
y = 122.24951171875.dp)
.requiredSize(size = 137.dp)
.clip(shape = CircleShape)
.background(color = Color(0xffb7b7e3).copy(alpha = 0.5f)))
}
}
@Preview(widthDp = 355, heightDp = 267)
@Composable
private fun RightCirclesPreview() {
RightCircles(Modifier)
}

View File

@ -0,0 +1,36 @@
package com.example.pmulabs.graphs
import androidx.compose.ui.Modifier
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import com.example.pmulabs.screensMobile.authScreens.EntryScreen
import com.example.pmulabs.screensMobile.authScreens.RegisterScreen
import com.example.pmulabs.screensMobile.authScreens.SplashScreen
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.EntryScreenViewModel
import com.example.pmulabs.viewModels.RegisterScreenViewModel
fun NavGraphBuilder.authNavGraph(navController: NavHostController,registerScreenViewModel: RegisterScreenViewModel,entryScreenViewModel: EntryScreenViewModel, currentUserViewModel: CurrentUserViewModel){
navigation(
route=Graph.AUTHENTICATION,
startDestination = AuthScreen.Splash.route
){
composable(route=AuthScreen.Splash.route){
SplashScreen(navController = navController, Modifier)
}
composable(route=AuthScreen.Entry.route){
EntryScreen(navController = navController, Modifier,entryScreenViewModel,currentUserViewModel)
}
composable(route=AuthScreen.Register.route){
RegisterScreen(navController = navController, Modifier,registerScreenViewModel,currentUserViewModel)
}
}
}
sealed class AuthScreen(val route: String){
object Splash : AuthScreen(route = "SPLASH")
object Entry : AuthScreen(route = "ENTRY")
object Register : AuthScreen(route="REGISTER")
}

View File

@ -0,0 +1,93 @@
package com.example.pmulabs.graphs
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.example.pmulabs.basecomponents.navigate.ARTICLE_ARGUMENT_KEY
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.basecomponents.navigate.CALENDAR_ARGUMENT_DATE
import com.example.pmulabs.basecomponents.navigate.SEARCHBYTAG_ARGUMENT_KEY
import com.example.pmulabs.basecomponents.navigate.SEARCH_ARGUMENT_TEXT
import com.example.pmulabs.screensMobile.ArticlePageScreen
import com.example.pmulabs.screensMobile.CoopScreen
import com.example.pmulabs.screensMobile.InfoScreen
import com.example.pmulabs.screensMobile.MainScreen
import com.example.pmulabs.screensMobile.ProfileScreen
import com.example.pmulabs.screensMobile.ReportScreen
import com.example.pmulabs.screensMobile.TagsScreen
import com.example.pmulabs.screensMobile.filterScreens.CalendarScreen
import com.example.pmulabs.screensMobile.filterScreens.SearchByTagScreen
import com.example.pmulabs.screensMobile.filterScreens.SearchScreen
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticlePageScreenViewModel
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.ReportViewModel
import com.example.pmulabs.viewModels.TagItemViewModel
@Composable
fun HomeNavGraph(navController: NavHostController,reportViewModel: ReportViewModel= viewModel(factory = AppViewModelProvider.Factory),articlePageScreenViewModel: ArticlePageScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), tagItemViewModel: TagItemViewModel = viewModel(factory = AppViewModelProvider.Factory), articleScreenViewModel: ArticleScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
NavHost(
navController = navController,
route = Graph.MAIN,
startDestination = BottomBarScreen.Main.route
){
composable(
route=BottomBarScreen.Main.route
){
MainScreen(navController,Modifier,articleScreenViewModel,currentUserViewModel)
}
composable(
route=BottomBarScreen.SearchByTag.route,
arguments = listOf(navArgument(SEARCHBYTAG_ARGUMENT_KEY){
type= NavType.StringType
})
){
SearchByTagScreen(navController,Modifier,articleScreenViewModel,currentUserViewModel)
}
composable(
route=BottomBarScreen.ArticlePage.route,
arguments = listOf(navArgument(ARTICLE_ARGUMENT_KEY){
type= NavType.StringType
})
){
ArticlePageScreen(navController,Modifier,articlePageScreenViewModel,currentUserViewModel)
}
composable(
route=BottomBarScreen.Search.route,
arguments = listOf(navArgument(SEARCH_ARGUMENT_TEXT){
type= NavType.StringType
})
){
SearchScreen(navController,Modifier,articleScreenViewModel,currentUserViewModel)
}
composable(
route=BottomBarScreen.Calendar.route,
arguments = listOf(navArgument(CALENDAR_ARGUMENT_DATE){
type= NavType.StringType
})
){
CalendarScreen(navController,articleScreenViewModel,Modifier,currentUserViewModel)
}
composable(route=BottomBarScreen.Profile.route){
ProfileScreen(navController,Modifier,articleScreenViewModel, tagItemViewModel, articlePageScreenViewModel,currentUserViewModel)
}
composable(route=BottomBarScreen.Info.route){
InfoScreen(navController,Modifier)
}
composable(route=BottomBarScreen.Cooperation.route){
CoopScreen(navController,Modifier)
}
composable(route=BottomBarScreen.Categories.route){
TagsScreen(navController,Modifier, tagItemViewModel, currentUserViewModel)
}
composable(route=BottomBarScreen.Report.route){
ReportScreen(navController, reportViewModel)
}
}
}

View File

@ -0,0 +1,47 @@
package com.example.pmulabs.graphs
import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.example.pmulabs.screensMobile.LoadScreen
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticlePageScreenViewModel
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.EntryScreenViewModel
import com.example.pmulabs.viewModels.RegisterScreenViewModel
import com.example.pmulabs.viewModels.ReportViewModel
import com.example.pmulabs.viewModels.SearchViewModel
import com.example.pmulabs.viewModels.TagItemViewModel
const val USERID_ARGUMENT="userId"
@Composable
fun RootNavigationGraph(navController: NavHostController,reportViewModel: ReportViewModel= viewModel(factory = AppViewModelProvider.Factory), searchViewModel: SearchViewModel,articlePageScreenViewModel: ArticlePageScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), tagItemViewModel: TagItemViewModel = viewModel(factory = AppViewModelProvider.Factory), articleScreenViewModel: ArticleScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), registerScreenViewModel: RegisterScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), entryScreenViewModel: EntryScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
NavHost(
navController=navController,
route = Graph.ROOT,
startDestination = Graph.AUTHENTICATION
){
authNavGraph(navController=navController,registerScreenViewModel,entryScreenViewModel,currentUserViewModel)
composable(route=Graph.MAIN,
arguments = listOf(navArgument(USERID_ARGUMENT){
type= NavType.StringType
})){
LoadScreen(searchViewModel = searchViewModel,reportViewModel=reportViewModel, articleScreenViewModel = articleScreenViewModel, articlePageScreenViewModel = articlePageScreenViewModel, tagItemViewModel = tagItemViewModel,currentUserViewModel = currentUserViewModel)
}
}
}
object Graph{
const val ROOT="root_graph"
const val AUTHENTICATION="auth_graph"
const val MAIN="main_graph/{$USERID_ARGUMENT}"
fun passUserId(userId: String): String{
return "main_graph/$userId"
}
}

View File

@ -0,0 +1,78 @@
package com.example.pmulabs.room
import android.content.Context
import com.example.pmulabs.api.NewsPortalService
import com.example.pmulabs.api.repository.RestArticleRepository
import com.example.pmulabs.api.repository.RestCommentRepository
import com.example.pmulabs.api.repository.RestTagRepository
import com.example.pmulabs.api.repository.RestUserRepository
import com.example.pmulabs.room.database.NewsPortalDatabase
import com.example.pmulabs.room.repository.OfflineArticleRepository
import com.example.pmulabs.room.repository.OfflineCommentRepository
import com.example.pmulabs.room.repository.OfflineRemoteKeyRepository
import com.example.pmulabs.room.repository.OfflineTagRepository
import com.example.pmulabs.room.repository.OfflineUserRepository
interface AppContainer {
val userRestRepository: RestUserRepository
val tagRestRepository: RestTagRepository
val articleRestRepository: RestArticleRepository
val commentRestRepository: RestCommentRepository
companion object {
const val TIMEOUT = 5000L
const val LIMIT = 10
}
}
class AppDataContainer(private val context: Context) : AppContainer {
private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(NewsPortalDatabase.getInstance(context).userDao())
}
private val articleRepository: OfflineArticleRepository by lazy {
OfflineArticleRepository(NewsPortalDatabase.getInstance(context).articleDao())
}
private val tagRepository: OfflineTagRepository by lazy {
OfflineTagRepository(NewsPortalDatabase.getInstance(context).tagDao())
}
private val commentRepository: OfflineCommentRepository by lazy {
OfflineCommentRepository(NewsPortalDatabase.getInstance(context).commentDao())
}
private val remoteKeyRepository: OfflineRemoteKeyRepository by lazy {
OfflineRemoteKeyRepository(NewsPortalDatabase.getInstance(context).remoteKeysDao())
}
override val userRestRepository: RestUserRepository by lazy {
RestUserRepository(
NewsPortalService.getInstance(),
userRepository,
commentRepository,
tagRepository,
articleRepository
)
}
override val tagRestRepository: RestTagRepository by lazy {
RestTagRepository(
NewsPortalService.getInstance(),
tagRepository,
remoteKeyRepository,
NewsPortalDatabase.getInstance(context)
)
}
override val articleRestRepository: RestArticleRepository by lazy {
RestArticleRepository(
NewsPortalService.getInstance(),
articleRepository,
remoteKeyRepository,
tagRestRepository,
NewsPortalDatabase.getInstance(context)
)
}
override val commentRestRepository: RestCommentRepository by lazy {
RestCommentRepository(
NewsPortalService.getInstance(),
commentRepository,
remoteKeyRepository,
articleRestRepository,
NewsPortalDatabase.getInstance(context)
)
}
}

View File

@ -0,0 +1,34 @@
package com.example.pmulabs.room.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.pmulabs.room.models.Article
import kotlinx.coroutines.flow.Flow
@Dao
interface ArticleDao {
@Query("select * from article")
fun getAll(): List<Article>
@Query("select * from article where article.id = :idArticle")
fun getArticleById(idArticle: Int): Flow<Article>
@Query("SELECT * FROM article ORDER BY id ASC")
fun getArticles(): PagingSource<Int, Article>
@Insert
suspend fun insert(vararg article: Article)
@Update
suspend fun update(article: Article)
@Delete
suspend fun delete(article: Article)
@Query("DELETE FROM tag")
suspend fun deleteAll()
}

View File

@ -0,0 +1,38 @@
package com.example.pmulabs.room.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
@Dao
interface CommentDao {
@Query("select * from comment")
fun getAll(): List<Comment>
@Query("select COUNT(*) from comment WHERE comment.text!='' AND comment.article_id= :idArticle")
fun getCountComment(idArticle : Int?) : Int
@Query("SELECT * FROM comment ORDER BY id ASC")
fun getComments(): PagingSource<Int, Comment>
@Query("SELECT * FROM user WHERE user.id=:commId")
fun getUser(commId:Int): Flow<User>
@Insert
suspend fun insert(vararg comment: Comment)
@Update
suspend fun update(comment: Comment)
@Delete
suspend fun delete(comment: Comment)
@Query("DELETE FROM tag")
suspend fun deleteAll()
}

View File

@ -0,0 +1,20 @@
package com.example.pmulabs.room.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
@Dao
interface RemoteKeysDao {
@Query("SELECT * FROM remote_keys WHERE entityId = :entityId AND type = :type")
suspend fun getRemoteKeys(entityId: Int, type: RemoteKeyType): RemoteKeys?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys?>)
@Query("DELETE FROM remote_keys WHERE type = :type")
suspend fun clearRemoteKeys(type: RemoteKeyType)
}

View File

@ -0,0 +1,37 @@
package com.example.pmulabs.room.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.pmulabs.room.models.Tag
import kotlinx.coroutines.flow.Flow
@Dao
interface TagDao {
@Query("select * from tag")
fun getAll(): List<Tag>
@Query("select * from tag where tag.id = :idTag")
fun getTagById(idTag: Int): Flow<Tag>
@Query("select * from tag where tag.title = :nameTag")
fun getTagByName(nameTag: String): Flow<Tag>
@Query("SELECT * FROM tag ORDER BY id ASC")
fun getTags(): PagingSource<Int, Tag>
@Insert
suspend fun insert(vararg tag: Tag)
@Update
suspend fun update(tag: Tag)
@Delete
suspend fun delete(tag: Tag)
@Query("DELETE FROM tag")
suspend fun deleteAll()
}

View File

@ -0,0 +1,39 @@
package com.example.pmulabs.room.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDao {
@Query("select * from user")
suspend fun getAll(): List<User>
@Query("select * from user where user.id = :idUser")
fun getUserById(idUser: Int?): Flow<User>
@Query("select * from comment WHERE comment.text!='' AND comment.user_id= :idUser")
suspend fun getUserComms(idUser: Int): List<Comment>
@Query("select * from article WHERE article.text!='' AND article.user_id= :idUser")
suspend fun getUserArticles(idUser: Int): List<Article>
@Query("select * from tag WHERE tag.title!='' AND tag.user_id= :idUser")
suspend fun getUserTags(idUser: Int): List<Tag>
@Insert
suspend fun insert(user: User)
@Update
suspend fun update(user: User)
@Delete
suspend fun delete(user: User)
}

View File

@ -0,0 +1,118 @@
package com.example.pmulabs.room.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.pmulabs.room.dao.ArticleDao
import com.example.pmulabs.room.dao.CommentDao
import com.example.pmulabs.room.dao.RemoteKeysDao
import com.example.pmulabs.room.dao.TagDao
import com.example.pmulabs.room.dao.UserDao
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.RemoteKeys
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import java.util.Date
@Database(entities = [RemoteKeys::class, User::class, Tag::class, Comment::class, Article::class], version = 5, exportSchema = false)
abstract class NewsPortalDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun tagDao(): TagDao
abstract fun commentDao(): CommentDao
abstract fun articleDao(): ArticleDao
abstract fun remoteKeysDao(): RemoteKeysDao
companion object {
private const val DB_NAME: String = "newsportaldb"
@Volatile
private var INSTANCE: NewsPortalDatabase? = null
private suspend fun populateDatabase() {
INSTANCE?.let { database ->
val userDao = database.userDao()
val user1 = User(1, "LatinMisha","llmisha@gmail.com","user1","user")
val user2 = User(2, "Kasablanka","kasyul@gmail.com","user2","user")
val user3 = User(3, "Ilonherber","ihlon82@gmail.com","user3","user")
userDao.insert(user1)
userDao.insert(user2)
userDao.insert(user3)
val tagDao = database.tagDao()
val tag1 = Tag(id=1, title = "Тег_1", userId = 2)
val tag2 = Tag(id=2, title ="Тег_2",userId = 1)
val tag3 = Tag(id=3, title ="Тег_3",userId = 3)
val tag4 = Tag(id=4, title ="Тег_4",userId = 2)
val tag5 = Tag(id=5, title ="Тег_5",userId = 1)
val tag6 = Tag(id=6, title ="Тег_6",userId = 3)
tagDao.insert(tag1)
tagDao.insert(tag2)
tagDao.insert(tag3)
tagDao.insert(tag4)
tagDao.insert(tag5)
tagDao.insert(tag6)
val articleDao = database.articleDao()
val article1 = Article(1, "Заголовок 1","Текст статьи с заголовком 1", Date(2023,5,22).time,1,6)
val article2 = Article(2, "Заголовок 2","Текст статьи с заголовком 2", Date(2022,11,13).time,3,2)
val article3 = Article(3, "Заголовок 3","Текст статьи с заголовком 3", Date(2023,5,4).time,2,1)
val article4 = Article(4, "Заголовок 4","Текст статьи с заголовком 4", Date(2023,2,14).time,3,3)
val article5 = Article(5, "Заголовок 5","Текст статьи с заголовком 5", Date(2023,0,19).time,1,4)
val article6 = Article(6, "Заголовок 6","Текст статьи с заголовком 6", Date(2023,5,22).time,2,4)
val article7 = Article(7, "Заголовок 7","Текст статьи с заголовком 7", Date(2023,0,19).time,3,5)
val article8 = Article(8, "Заголовок 8","Текст статьи с заголовком 8", Date(2022,11,13).time,1,6)
articleDao.insert(article1)
articleDao.insert(article2)
articleDao.insert(article3)
articleDao.insert(article4)
articleDao.insert(article5)
articleDao.insert(article6)
articleDao.insert(article7)
articleDao.insert(article8)
val commentDao = database.commentDao()
val comment1 = Comment(1, "Текст комментария 1",1,8)
val comment2 = Comment(2, "Текст комментария 2",2,2)
val comment3 = Comment(3, "Текст комментария 3",3,3)
val comment4 = Comment(4, "Текст комментария 4",1,4)
val comment5 = Comment(5, "Текст комментария 5",2,1)
val comment6 = Comment(6, "Текст комментария 6",3,4)
val comment7 = Comment(7, "Текст комментария 7",1,6)
val comment8 = Comment(8, "Текст комментария 8",2,7)
val comment9 = Comment(9, "Текст комментария 9",3,1)
val comment10 = Comment(10, "Текст комментария 10",1,2)
val comment11 = Comment(11, "Текст комментария 11",2,3)
commentDao.insert(comment1)
commentDao.insert(comment2)
commentDao.insert(comment3)
commentDao.insert(comment4)
commentDao.insert(comment5)
commentDao.insert(comment6)
commentDao.insert(comment7)
commentDao.insert(comment8)
commentDao.insert(comment9)
commentDao.insert(comment10)
commentDao.insert(comment11)
}
}
fun getInstance(appContext: Context): NewsPortalDatabase {
return INSTANCE ?: synchronized(this) {
Room.databaseBuilder(
appContext,
NewsPortalDatabase::class.java,
DB_NAME
)
/*.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
CoroutineScope(Dispatchers.IO).launch {
populateDatabase()
}
}
})*/
.build()
.also { INSTANCE = it }
}
}
}
}

View File

@ -0,0 +1,37 @@
package com.example.pmulabs.room.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "article",
foreignKeys = [ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE),
ForeignKey(entity = Tag::class, parentColumns = ["id"], childColumns = ["tag_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE)])
data class Article(
@PrimaryKey(autoGenerate = true)
val id: Int?,
@ColumnInfo(name = "title")
var title: String,
@ColumnInfo(name = "text")
var text: String,
@ColumnInfo(name = "publish_date")
val publishDate: Long,
@ColumnInfo(name = "user_id")
val userId: Int,
@ColumnInfo(name = "tag_id")
var tagId: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Article
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,32 @@
package com.example.pmulabs.room.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
@Entity(tableName = "comment",
foreignKeys = [ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE),
ForeignKey(entity = Article::class, parentColumns = ["id"], childColumns = ["article_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE)])
data class Comment(
@PrimaryKey(autoGenerate = true)
val id: Int?,
@ColumnInfo(name = "text")
var text: String,
@ColumnInfo(name = "user_id")
val userId: Int,
@ColumnInfo(name = "article_id")
val articleId: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Comment
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,27 @@
package com.example.pmulabs.room.models
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverter
import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) {
ARTICLE(Article::class.simpleName ?: "Article"),
TAG(Tag::class.simpleName ?: "Tag"),
COMMENT(Comment::class.simpleName ?: "Comment");
@TypeConverter
fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value }
@TypeConverter
fun fromRemoteKeyType(value: RemoteKeyType) = value.type
}
@Entity(tableName = "remote_keys")
data class RemoteKeys(
@PrimaryKey val entityId: Int,
@TypeConverters(RemoteKeyType::class)
val type: RemoteKeyType,
val prevKey: Int?,
val nextKey: Int?
)

View File

@ -0,0 +1,31 @@
package com.example.pmulabs.room.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "tag",
foreignKeys = [ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.CASCADE)],
indices = [Index(value = ["title"], unique = true)])
data class Tag(
@ColumnInfo(name = "title")
var title: String,
@PrimaryKey(autoGenerate = true)
val id: Int?,
@ColumnInfo(name = "user_id")
val userId: Int
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Tag
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,35 @@
package com.example.pmulabs.room.models
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "user",
indices = [Index(value = ["email"], unique = true),
Index(value = ["nickname"], unique = true)
])
data class User(
@PrimaryKey(autoGenerate = true)
val id: Int?,
@ColumnInfo(name = "nickname")
var nickname: String,
@ColumnInfo(name = "email")
var email: String,
@ColumnInfo(name = "password")
var password: String,
@ColumnInfo(name = "role")
val role: String
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as User
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id ?: -1
}
}

View File

@ -0,0 +1,14 @@
package com.example.pmulabs.room.repository
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Article
import kotlinx.coroutines.flow.Flow
interface ArticleRepository {
suspend fun insertArticle(article: Article)
suspend fun updateArticle(article: Article)
suspend fun deleteArticle(article: Article)
suspend fun getAllArticles(): List<Article>
suspend fun getArticleById(idArticle: Int): Article?
fun getArticles(): Flow<PagingData<Article>>
}

View File

@ -0,0 +1,16 @@
package com.example.pmulabs.room.repository
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.Flow
interface CommentRepository {
suspend fun insertComment(comment: Comment)
suspend fun updateComment(comment: Comment)
suspend fun deleteComment(comment: Comment)
suspend fun getAllComments(): List<Comment>
suspend fun getCountComment(idArticle : Int?) : Int?
fun getComments(): Flow<PagingData<Comment>>
suspend fun getUser(commId: Int): User?
}

View File

@ -0,0 +1,31 @@
package com.example.pmulabs.room.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.pmulabs.room.dao.ArticleDao
import com.example.pmulabs.room.models.Article
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineArticleRepository(private val articleDao: ArticleDao) : ArticleRepository {
override suspend fun insertArticle(article: Article) = articleDao.insert(article)
override suspend fun updateArticle(article: Article) = articleDao.update(article)
override suspend fun deleteArticle(article: Article) = articleDao.delete(article)
override suspend fun getAllArticles(): List<Article> = articleDao.getAll()
override suspend fun getArticleById(idArticle: Int): Article? = articleDao.getArticleById(idArticle).first()
override fun getArticles(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(
pageSize = 4,
enablePlaceholders = false
),
pagingSourceFactory = articleDao::getArticles
).flow
fun getAllArticlesPagingSource(): PagingSource<Int, Article> = articleDao.getArticles()
suspend fun clearArticles() = articleDao.deleteAll()
suspend fun insertArticles(articles: List<Article>) =
articleDao.insert(*articles.toTypedArray())
}

View File

@ -0,0 +1,36 @@
package com.example.pmulabs.room.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.pmulabs.room.dao.CommentDao
import com.example.pmulabs.room.models.Comment
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineCommentRepository(private val commentDao: CommentDao) : CommentRepository {
override suspend fun insertComment(comment: Comment) = commentDao.insert(comment)
override suspend fun updateComment(comment: Comment) = commentDao.update(comment)
override suspend fun deleteComment(comment: Comment) = commentDao.delete(comment)
override suspend fun getAllComments(): List<Comment> = commentDao.getAll()
override suspend fun getCountComment(idArticle : Int?) : Int = commentDao.getCountComment(idArticle)
override fun getComments(): Flow<PagingData<Comment>> = Pager(
config = PagingConfig(
pageSize = 20,
enablePlaceholders = false
),
pagingSourceFactory =commentDao::getComments
).flow
fun getAllCommentsPagingSource(): PagingSource<Int, Comment> = commentDao.getComments()
suspend fun clearComments() = commentDao.deleteAll()
override suspend fun getUser(commId: Int) = commentDao.getUser(commId).first()
suspend fun insertComments(comments: List<Comment>) {
commentDao.insert(*comments.toTypedArray())
}
}

View File

@ -0,0 +1,16 @@
package com.example.pmulabs.room.repository
import com.example.pmulabs.room.dao.RemoteKeysDao
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
class OfflineRemoteKeyRepository(private val remoteKeysDao: RemoteKeysDao) : RemoteKeyRepository {
override suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType) =
remoteKeysDao.getRemoteKeys(id, type)
override suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys?>) =
remoteKeysDao.insertAll(remoteKeys)
override suspend fun deleteRemoteKey(type: RemoteKeyType) =
remoteKeysDao.clearRemoteKeys(type)
}

View File

@ -0,0 +1,34 @@
package com.example.pmulabs.room.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.example.pmulabs.room.AppContainer
import com.example.pmulabs.room.dao.TagDao
import com.example.pmulabs.room.models.Tag
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
class OfflineTagRepository(private val tagDao: TagDao) : TagRepository {
override suspend fun insertTag(tag: Tag) = tagDao.insert(tag)
override suspend fun updateTag(tag: Tag) = tagDao.update(tag)
override suspend fun deleteTag(tag: Tag) = tagDao.delete(tag)
override suspend fun getAllTags(): List<Tag> = tagDao.getAll()
override suspend fun getTagById(idTag: Int): Tag? = tagDao.getTagById(idTag).first()
override suspend fun getTagByName(nameTag: String): Tag? = tagDao.getTagByName(nameTag).first()
override fun getTags(): Flow<PagingData<Tag>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = tagDao::getTags
).flow
fun getAllTagsPagingSource(): PagingSource<Int, Tag> = tagDao.getTags()
suspend fun clearTags() = tagDao.deleteAll()
suspend fun insertTags(tags: List<Tag>) =
tagDao.insert(*tags.toTypedArray())
}

View File

@ -0,0 +1,24 @@
package com.example.pmulabs.room.repository
import com.example.pmulabs.api.model.ReportRemote
import com.example.pmulabs.room.dao.UserDao
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import kotlinx.coroutines.flow.first
class OfflineUserRepository(private val userDao: UserDao) : UserRepository {
override suspend fun insertUser(user: User) = userDao.insert(user)
override suspend fun updateUser(user: User) = userDao.update(user)
override suspend fun deleteUser(user: User) = userDao.delete(user)
override suspend fun getAllUsers(): List<User> = userDao.getAll()
override suspend fun getUserById(idUser: Int?): User? = userDao.getUserById(idUser).first()
override suspend fun getUserComms(idUser: Int): List<Comment> = userDao.getUserComms(idUser)
override suspend fun getUserArticles(idUser: Int): List<Article> = userDao.getUserArticles(idUser)
override suspend fun getUserTags(idUser: Int): List<Tag> = userDao.getUserTags(idUser)
override suspend fun getReport(startDate: Long, endDate: Long): List<ReportRemote> {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,10 @@
package com.example.pmulabs.room.repository
import com.example.pmulabs.room.models.RemoteKeyType
import com.example.pmulabs.room.models.RemoteKeys
interface RemoteKeyRepository {
suspend fun getAllRemoteKeys(id: Int, type: RemoteKeyType): RemoteKeys?
suspend fun createRemoteKeys(remoteKeys: List<RemoteKeys?>)
suspend fun deleteRemoteKey(type: RemoteKeyType)
}

View File

@ -0,0 +1,15 @@
package com.example.pmulabs.room.repository
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Tag
import kotlinx.coroutines.flow.Flow
interface TagRepository {
suspend fun insertTag(tag: Tag)
suspend fun updateTag(tag: Tag)
suspend fun deleteTag(tag: Tag)
suspend fun getAllTags(): List<Tag>
suspend fun getTagById(idTag: Int): Tag?
suspend fun getTagByName(nameTag: String): Tag?
fun getTags(): Flow<PagingData<Tag>>
}

View File

@ -0,0 +1,19 @@
package com.example.pmulabs.room.repository
import com.example.pmulabs.api.model.ReportRemote
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
interface UserRepository {
suspend fun insertUser(user: User)
suspend fun updateUser(user: User)
suspend fun deleteUser(user: User)
suspend fun getAllUsers(): List<User>
suspend fun getUserById(idUser: Int?): User?
suspend fun getUserComms(idUser: Int): List<Comment>
suspend fun getUserArticles(idUser: Int): List<Article>
suspend fun getUserTags(idUser: Int): List<Tag>
suspend fun getReport(startDate: Long, endDate: Long):List<ReportRemote>
}

View File

@ -0,0 +1,308 @@
package com.example.pmulabs.screensMobile
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.basecomponents.navigate.ARTICLE_ARGUMENT_KEY
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.designElem.elem.BackButton
import com.example.pmulabs.designElem.items.CommentItem
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticlePageScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ArticlePageScreen(navController: NavController, modifier: Modifier = Modifier,articlePageScreenViewModel: ArticlePageScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
articlePageScreenViewModel.setUserList()
var id =
navController.currentBackStackEntry?.arguments?.getString(ARTICLE_ARGUMENT_KEY).toString()
try {
articlePageScreenViewModel.getArticleById(id.toInt())
} catch (e: NumberFormatException) {
e.printStackTrace()
}
var textComm by rememberSaveable { mutableStateOf("") }
var article by mutableStateOf<Article?>(articlePageScreenViewModel.article)
try {
article?.tagId?.let { articlePageScreenViewModel.getTagById(it) }
} catch (e: Exception) {
e.printStackTrace()
}
var tag by mutableStateOf<Tag?>(articlePageScreenViewModel.tag)
Log.d("Tag:", tag.toString())
var tagId = articlePageScreenViewModel.tag?.id
val formatter = SimpleDateFormat("dd.MM.YY")
val publishDate = formatter.format(Date(article?.publishDate ?: 0))
val comms = articlePageScreenViewModel.comments.collectAsLazyPagingItems()
var users = mutableStateOf( articlePageScreenViewModel.userList)
Log.d("ListOfUsers", users.toString())
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
val coroutineScope = rememberCoroutineScope()
LazyColumn(
contentPadding = PaddingValues(top = 175.dp, bottom = 90.dp, start = 15.dp, end = 15.dp),
modifier = modifier
.requiredWidth(width = 362.dp)
.requiredHeight(height = 915.dp)
.background(color = Color.White)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
item {
BackButton(modifier = Modifier
.clickable { navController.navigate(BottomBarScreen.Main.route) }
.offset(
y = 0.dp
))
}
item {
Row(
modifier = Modifier
.requiredWidth(width = 290.dp)
.requiredHeight(height = 33.dp)
.offset(
y = 0.dp
)
) {
Text(
text = "${publishDate}",
color = Color(0xff423a99),
style = MaterialTheme.typography.headlineMedium
)
Text(
text = "#${tag?.title}",
color = Color(0xff423a99),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.offset(
x = 20.dp,
y = 0.dp
)
)
}
}
item {
Text(
text = "${article?.title}",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 32.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.offset(
x = 5.dp,
y = 0.dp
)
.requiredWidth(width = 335.dp)
)
}
item {
Text(
text = "${article?.text}",
color = Color(0xff423a99),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.offset(
x = 5.dp,
y = 0.dp
)
.requiredWidth(width = 315.dp)
)
}
item {
Box(
modifier = modifier
.requiredWidth(width = 352.dp)
.requiredHeight(height = 53.dp)
.offset(
y = 0.dp
)
) {
Text(
text = "Comments:",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 32.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 15.dp,
y = 6.dp
)
.requiredWidth(width = 335.dp)
.requiredHeight(height = 43.dp)
)
HorizontalDivider(
thickness = 5.dp,
modifier = Modifier
.border(BorderStroke(3.dp, Color(0xff423a99))), color = Color(0xff423a99)
)
HorizontalDivider(
thickness = 5.dp,
modifier = Modifier
.align(alignment = Alignment.TopStart)
.border(BorderStroke(3.dp, Color(0xff423a99)))
.offset(
y = 53.dp
), color = Color(0xff423a99)
)
}
}
item {
OutlinedTextField(
value = textComm,
onValueChange = {
textComm = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Comment") },
placeholder = { Text("Write comment...") },
minLines = 3,
maxLines = 3,
)
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
if (textComm.isNotEmpty()) {
var newComment =
Comment(null, textComm, getUser?.id.toString().toInt(), id.toInt())
coroutineScope.launch {
articlePageScreenViewModel.insertComment(newComment)
}
textComm = ""
navController.navigate(BottomBarScreen.ArticlePage.passId(article?.id.toString()))
}
},
modifier = Modifier
.padding(start = 230.dp)
.fillMaxWidth(0.9f)
.height(40.dp)
) {
Text(text = "Send", fontSize = 18.sp)
}
}
items(count = comms.itemCount) { index ->
val comm = comms[index]
val user = comm?.userId?.let { users.value.get(it-1) }!!
if (comm != null) {
if (comm.articleId == article?.id && comm.text != "") {
Spacer(modifier = Modifier.padding(0.dp))
HorizontalDivider(
thickness = 3.dp,
modifier = Modifier
.border(BorderStroke(3.dp, Color(0xff423a99))), color = Color(0xff423a99)
)
CommentItem(navController,comm = comm,user=user, currentUserViewModel = currentUserViewModel)
}
}
}
comms.apply {
when {
loadState.refresh is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier.fillParentMaxSize(),
color = Color(0xff423a99)
)
}
}
loadState.append is LoadState.Loading -> {
item {
CircularProgressIndicator(
modifier = Modifier.fillParentMaxSize(),
color = Color(0xff423a99)
)
}
}
loadState.refresh is LoadState.Error -> {
val err = comms.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = comms.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}

View File

@ -0,0 +1,200 @@
package com.example.pmulabs.screensMobile
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
@Composable
fun CoopScreen(navController: NavController,modifier: Modifier = Modifier) {
LazyColumn(
modifier = modifier
.background(Color.White),
contentPadding = PaddingValues(top =80.dp, bottom = 105.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
item {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 275.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 21.810302734375.dp)
.requiredWidth(width = 333.dp)
.requiredHeight(height = 253.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 253.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)))
Text(
text = " Мы предлагаем рекламные размещения под любой бюджет и аудиторию. \n\n На ресурсе самые лучшие посетители. У нас нет \"школьников\", \"мимо проходящих\", ботов, \"просто интересующихся\". Нас читают те, кто глубоко погружен в технику.\n\n У нас много трафика и нормальные цены за нормальную отдачу,а также рекламные возможности на любой бюджет. ",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 14.dp,
y = 34.189697265625.dp)
.requiredWidth(width = 310.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99)))
Text(
text = "О рекламе",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 24.dp,
y = 0.dp)
.requiredWidth(width = 210.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
}
}
item {
Box(
modifier = Modifier
.offset(x = 0.dp,
y = 11.dp)
.requiredWidth(width = 344.dp)
.requiredHeight(height = 420.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 21.dp)
.requiredWidth(width = 344.dp)
.requiredHeight(height = 420.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 420.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)))
Text(
text = buildAnnotatedString {
withStyle(style = SpanStyle(
color = Color(0xff423a99),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("КОММЕРЧЕСКИЙ ДИРЕКТОР:\n")}
withStyle(style = SpanStyle(
color = Color(0xff847fbe),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("Александр Воробьёв\nvorobiev@ixbt.com\n+7 (499) 519-00-95\n")}
withStyle(style = SpanStyle(
color = Color(0xff423a99),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("ДИРЕКТОР ПО РЕКЛАМЕ:\n")}
withStyle(style = SpanStyle(
color = Color(0xff847fbe),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("Владимир Сливко\nvslivko@ixbt.com\n+7 (916) 134-14-72\n")}
withStyle(style = SpanStyle(
color = Color(0xff423a99),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("ПРОДАКТ-МЕНЕДЖЕР:")}
withStyle(style = SpanStyle(
color = Color(0xff423a99),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append(" \n")}
withStyle(style = SpanStyle(
color = Color(0xff847fbe),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("Дарья Галиева\ndgalieva@ixbt.games\n+7 (499) 519-00-95\n")}
withStyle(style = SpanStyle(
color = Color(0xff423a99),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("ГЛАВНЫЙ РЕДАКТОР:\n")}
withStyle(style = SpanStyle(
color = Color(0xff847fbe),
fontSize = 15.sp,
fontWeight = FontWeight.Bold)) {append("Виталий Казунов\nlock@ixbt.com\n+7 (499) 519-00-95")}},
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 11.dp,
y = 10.dp)
.requiredWidth(width = 333.dp)
.requiredHeight(height = 420.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 42.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 42.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99)))
Text(
text = "Контакты",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 24.dp,
y = 0.dp)
.requiredWidth(width = 210.dp)
.requiredHeight(height = 42.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
}
}
}
}

View File

@ -0,0 +1,230 @@
package com.example.pmulabs.screensMobile
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
@Composable
fun InfoScreen(navController: NavController,modifier: Modifier = Modifier) {
LazyColumn(
modifier = modifier
.background(Color.White),
contentPadding = PaddingValues(top =75.dp, bottom = 85.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(15.dp)
) {
item {
Box(
modifier = Modifier
.requiredWidth(width = 340.dp)
.requiredHeight(height = 309.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 22.dp)
.requiredWidth(width = 333.dp)
.requiredHeight(height = 287.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 287.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)))
Text(
text=" iXBT.com — специализированный российский информационно-аналитический сайт с самыми актуальными новостями из сферы компьютерных и мобильных игр, развлекательной и интернет-индустрии, детальными обзорами персональных компьютеров, компьютерных комплектующих, периферийных устройств и техники для гейминга. Мы работаем для вас с 2007 года.\n Наш адрес: \"115201, Москва, Каширское шоссе, д. 22 кор. 3 стр. 2; тел. +7(499)519-00-95\"",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 12.dp,
y = 27.dp)
.requiredWidth(width = 304.dp)
.requiredHeight(height = 250.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99)))
Text(
text = "О сайте iXBT.com",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 24.dp,
y = 0.dp)
.requiredWidth(width = 210.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
}
}
item {
Box(
modifier = Modifier
.offset(x = 0.dp,
y = 15.dp)
.requiredWidth(width = 340.dp)
.requiredHeight(height = 200.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 23.dp)
.requiredWidth(width = 333.dp)
.requiredHeight(height = 177.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 177.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)))
Text(
text = " Информация, размещенная на сайте iXBT.games, может быть изменена без предварительного уведомления. Все названия компаний, их логотипы и названия продуктов являются зарегистрированными торговыми марками соответствующих компаний.",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 12.dp,
y = 20.dp)
.requiredWidth(width = 304.dp)
.requiredHeight(height = 144.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 42.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 42.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99)))
Text(
text = "Прежде всего",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 24.dp,
y = 0.dp)
.requiredWidth(width = 210.dp)
.requiredHeight(height = 42.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
}
}
item {
Box(
modifier = Modifier
.offset(x = 0.dp,
y = 25.dp)
.requiredWidth(width = 340.dp)
.requiredHeight(height = 800.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 0.dp,
y = 0.dp)
.requiredWidth(width = 333.dp)
.requiredHeight(height = 800.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 759.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(color = Color.White)
.border(border = BorderStroke(3.dp, Color(0xffdbdbf1)),
shape = RoundedCornerShape(5.dp)))
Text(
text = " Проект iXBT.games (Ай-Экс-Би-Ти Дот Геймс) является частью проекта iXBT.com (Ай-Экс-Би-Ти Дот Ком) - интернет-портала, освещающего новости ИТ-индустрии, технологий, техники, ПО и сети Интернет.\n\n Компания «Аспект Исследования и Публикации» (Aspect Research and Publishing, Ltd) имеет свидетельство о регистрации средства массовой информации iXBT.com (Ай-Экс-Би-Ти Дот Ком) за номером: ЭЛ № ФС 77 - 56280 от 28 ноября 2013 года. Свидетельство выдано Федеральной службой по надзору в сфере связи, информационных технологий и массовых коммуникаций в соответствии с Законом Российской Федерации от 27 декабря 1991 года.\n\n Ранее электронное периодическое издание iXBT.com (Ай-Экс-Би-Ти Дот Ком) было зарегистрировано за номером: ЭЛ № ФС77-22565 от 20 декабря 2005. СМИ перерегистрировано в связи со сменой учредителя 28 ноября 2013 года.\n\n Ранее электронное периодическое издание «Ай-Экс-Би-Ти дот ком / iXBT.com» («iXBT.com / Ай-Экс-Би-Ти дот ком») зарегистрировано Министерством Российской Федерации по делам печати, телерадиовещания и средств массовых коммуникаций — свидетельство ЭЛ № 77-6328 от 10 июня 2002 года. СМИ перерегистрировано в связи со сменой учредителя и изменением названия 20 декабря 2005 года.",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 12.dp,
y = 30.dp)
.requiredWidth(width = 308.dp)
.requiredHeight(height = 727.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 42.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 333.dp)
.requiredHeight(height = 42.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99)))
Text(
text = "Правовые основы деятельности сайта",
color = Color.White,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(x = 24.dp,
y = 0.dp)
.requiredWidth(width = 294.dp)
.requiredHeight(height = 42.dp)
.wrapContentHeight(align = Alignment.CenterVertically))
}
}
}
}
}

View File

@ -0,0 +1,295 @@
package com.example.pmulabs.screensMobile
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerColors
import androidx.compose.material3.DatePickerDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.basecomponents.navigate.BottomBar
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.basecomponents.navigate.SearchAppBar
import com.example.pmulabs.basecomponents.navigate.TopBar
import com.example.pmulabs.designElem.items.ArticleItem
import com.example.pmulabs.graphs.HomeNavGraph
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticlePageScreenViewModel
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.ReportViewModel
import com.example.pmulabs.viewModels.SearchViewModel
import com.example.pmulabs.viewModels.SearchWidget
import com.example.pmulabs.viewModels.TagItemViewModel
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
@Composable
fun MainScreen(navController: NavController, modifier: Modifier = Modifier, articleScreenViewModel: ArticleScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val articles = articleScreenViewModel.articles.collectAsLazyPagingItems()
LazyColumn(
modifier=Modifier
.background(Color.White)
.fillMaxSize(),
contentPadding = PaddingValues(top=75.dp, bottom = 70.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(15.dp)){
items(count = articles.itemCount) { index ->
val article = articles[index]
if (article != null) {
ArticleItem(navController=navController,article = article, currentUserViewModel = currentUserViewModel, onArticleClick = {
navController.navigate(BottomBarScreen.ArticlePage.passId(article.id.toString()))
})
}
}
articles.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.refresh is LoadState.Error -> {
val err = articles.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = articles.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoadScreen(navController: NavHostController=rememberNavController(), reportViewModel: ReportViewModel= viewModel(factory = AppViewModelProvider.Factory),articlePageScreenViewModel: ArticlePageScreenViewModel, tagItemViewModel: TagItemViewModel, articleScreenViewModel: ArticleScreenViewModel, searchViewModel: SearchViewModel, currentUserViewModel: CurrentUserViewModel){
val searchWidgetState by searchViewModel.searchWidgetState
val searchTextState by searchViewModel.searchTextState
val formatter = SimpleDateFormat("dd-MMMM-YY")
val calendar = Calendar.getInstance()
calendar.set(2023,11,28)
// set the initial date
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = calendar.timeInMillis)
var showDatePicker by remember {
mutableStateOf(false)
}
var selectedDate by remember {
mutableLongStateOf(calendar.timeInMillis) // or use mutableStateOf(calendar.timeInMillis)
}
if (showDatePicker) {
DatePickerDialog(
colors= DatePickerColors(
containerColor = Color.White,
currentYearContentColor=Color(0xff423a99),
dateTextFieldColors = TextFieldDefaults.textFieldColors(
Color(0xff423a99),
containerColor = Color.White,
focusedLabelColor = Color(0xff423a99),
focusedPlaceholderColor = Color(0xff423a99),
focusedTrailingIconColor = Color(0xff423a99)
),
dayContentColor=Color(0xff423a99),
dayInSelectionRangeContainerColor=Color(0xff423a99),
dayInSelectionRangeContentColor=Color(0xff423a99),
disabledDayContentColor=Color(0xff423a99),
disabledSelectedDayContainerColor=Color(0xff423a99),
disabledSelectedDayContentColor=Color(0xff423a99),
disabledSelectedYearContainerColor=Color(0xff423a99),
disabledSelectedYearContentColor=Color(0xff423a99),
disabledYearContentColor=Color(0xff423a99),
dividerColor=Color(0xff423a99),
headlineContentColor=Color(0xff423a99),
navigationContentColor=Color(0xff423a99),
selectedDayContainerColor=Color(0xff423a99),
selectedDayContentColor=Color.White,
selectedYearContainerColor=Color(0xff423a99),
selectedYearContentColor=Color.White,
subheadContentColor=Color(0xff423a99),
titleContentColor=Color(0xff423a99),
todayContentColor=Color(0xff423a99),
todayDateBorderColor=Color(0xff423a99),
weekdayContentColor=Color(0xff423a99),
yearContentColor=Color(0xff423a99)
),
onDismissRequest = {
showDatePicker = false
},
confirmButton = {
TextButton(
modifier=Modifier
.background(Color.White),
colors = ButtonColors(
contentColor = Color(0xff423a99),
containerColor=Color.Transparent,
disabledContainerColor=Color.Transparent,
disabledContentColor= Color.Transparent
),
onClick = {
showDatePicker = false
selectedDate = datePickerState.selectedDateMillis!!
Log.d("Selected date: ", formatter.format(Date(selectedDate)))
navController.navigate(BottomBarScreen.Calendar.passDate(formatter.format(Date(selectedDate)))){
popUpTo(BottomBarScreen.Main.route)
}
}) {
Text(text = "Confirm")
}
},
dismissButton = {
TextButton(
modifier=Modifier
.background(Color.White),
colors = ButtonColors(
contentColor = Color(0xff423a99),
containerColor=Color.Transparent,
disabledContainerColor=Color.Transparent,
disabledContentColor= Color.Transparent
),
onClick = {
showDatePicker = false
navController.navigate(BottomBarScreen.Main.route)
}) {
Text(text = "Cancel")
}
}
) {
DatePicker(
modifier= Modifier
.background(Color.White)
.clip(RoundedCornerShape(15.dp)),
colors = DatePickerColors(
containerColor = Color.White,
currentYearContentColor=Color(0xff423a99),
dateTextFieldColors = TextFieldDefaults.textFieldColors(
Color(0xff423a99),
containerColor = Color.White,
focusedLabelColor = Color(0xff423a99),
focusedPlaceholderColor = Color(0xff423a99),
focusedTrailingIconColor = Color(0xff423a99)
),
dayContentColor=Color(0xff423a99),
dayInSelectionRangeContainerColor=Color(0xff423a99),
dayInSelectionRangeContentColor=Color(0xff423a99),
disabledDayContentColor=Color(0xff423a99),
disabledSelectedDayContainerColor=Color(0xff423a99),
disabledSelectedDayContentColor=Color(0xff423a99),
disabledSelectedYearContainerColor=Color(0xff423a99),
disabledSelectedYearContentColor=Color(0xff423a99),
disabledYearContentColor=Color(0xff423a99),
dividerColor=Color(0xff423a99),
headlineContentColor=Color(0xff423a99),
navigationContentColor=Color(0xff423a99),
selectedDayContainerColor=Color(0xff423a99),
selectedDayContentColor=Color.White,
selectedYearContainerColor=Color(0xff423a99),
selectedYearContentColor=Color.White,
subheadContentColor=Color(0xff423a99),
titleContentColor=Color(0xff423a99),
todayContentColor=Color(0xff423a99),
todayDateBorderColor=Color(0xff423a99),
weekdayContentColor=Color(0xff423a99),
yearContentColor=Color(0xff423a99)
),
state = datePickerState
)
}
}
Scaffold(
bottomBar = { BottomBar(navController = navController) },
topBar = { MainAppBar(searchWidgetState= searchWidgetState,
searchTextState= searchTextState,
onTextChange= { searchViewModel.updateSearchTextState(newValue = it)
if(it!="") {
navController.navigate(BottomBarScreen.Search.passText(it))
}},
onCloseClicked= {
searchViewModel.updateSearchWidgetState(newValue = SearchWidget.CLOSED)
navController.navigate(BottomBarScreen.Main.route)
},
onSearchClicked= {
navController.navigate(BottomBarScreen.Search.passText(it))
},
onSearchTriggered= {
searchViewModel.updateSearchWidgetState(newValue = SearchWidget.OPENED)
},
onCalendarTriggered = {
showDatePicker = true
})}
) {
Modifier
.padding(it)
HomeNavGraph(navController = navController, reportViewModel = reportViewModel,articlePageScreenViewModel, tagItemViewModel,articleScreenViewModel,currentUserViewModel)
}
}
@Composable
fun MainAppBar(
searchWidgetState: SearchWidget,
searchTextState: String,
onTextChange: (String) -> Unit,
onCloseClicked: () -> Unit,
onSearchClicked: (String) -> Unit,
onSearchTriggered: () -> Unit,
onCalendarTriggered: () -> Unit
) {
when (searchWidgetState) {
SearchWidget.CLOSED -> {
TopBar(
onSearchClicked = onSearchTriggered,
onCalendarClicked = onCalendarTriggered
)
}
SearchWidget.OPENED -> {
SearchAppBar(
text = searchTextState,
onTextChange = onTextChange,
onCloseClicked = onCloseClicked,
onSearchClicked = onSearchClicked
)
}
}
}

View File

@ -0,0 +1,548 @@
package com.example.pmulabs.screensMobile
import android.annotation.SuppressLint
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.toSize
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.R
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.designElem.elem.ValidateEmail
import com.example.pmulabs.designElem.elem.isValidEmail
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticlePageScreenViewModel
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.TagItemViewModel
import kotlinx.coroutines.launch
import java.util.Date
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProfileScreen(navController: NavController, modifier: Modifier = Modifier,articleScreenViewModel: ArticleScreenViewModel= viewModel(factory = AppViewModelProvider.Factory),tagItemViewModel: TagItemViewModel= viewModel(factory = AppViewModelProvider.Factory),articlePageScreenViewModel: ArticlePageScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
var openDialogUser by remember { mutableStateOf(false) }
var email by remember { mutableStateOf(getUser?.email) }
var password by remember { mutableStateOf(getUser?.password) }
var nickname by remember { mutableStateOf(getUser?.nickname) }
val coroutineScope = rememberCoroutineScope()
articlePageScreenViewModel.setTagList()
val tags = mutableStateOf<List<Tag>>(articlePageScreenViewModel.tagList)
//список названий тэгов
var tagsNames = remember { mutableStateListOf<String>() }
tags.value.forEach { teg ->
if (!tagsNames.contains(teg.title)) {
tagsNames.add(teg.title)
}
}
var openDialog by remember { mutableStateOf(false) }
var text by remember { mutableStateOf("") }
var title by remember { mutableStateOf("") }
var tagId by remember { mutableStateOf(0) }
//переменные для dropdown
var expanded by remember { mutableStateOf(false) }
val suggestions = tagsNames
var selectedText by remember { mutableStateOf("") }
var textfieldSize by remember { mutableStateOf(Size.Zero) }
val icon = if (expanded)
Icons.Filled.ArrowDropUp
else
Icons.Filled.ArrowDropDown
if (openDialog) {
AlertDialog(
onDismissRequest = {
openDialog = false
text = ""
title = ""
},
content = {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(16.dp),
) {
Text(
text = "Add your article",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 20.sp,
),
)
Box() {
OutlinedTextField(
value = selectedText,
onValueChange = { selectedText = it },
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.onGloballyPositioned { coordinates ->
textfieldSize = coordinates.size.toSize()
},
label = { Text("Select Tag") },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
trailingIcon = {
Icon(icon, "List of tags",
Modifier.clickable { expanded = !expanded })
}
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.width(with(LocalDensity.current) { textfieldSize.width.toDp() })
) {
suggestions.forEach { label ->
DropdownMenuItem(
text = { Text(text = label) },
onClick = {
selectedText = label
coroutineScope.launch {
tagId =
articlePageScreenViewModel.getTagByName(selectedText)?.id!!
}
}
)
}
}
}
OutlinedTextField(
value = title,
onValueChange = {
title = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Article Title") },
placeholder = { Text("Write title of article...") },
minLines = 3,
maxLines = 3,
)
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Article Text") },
placeholder = { Text("Write article text...") },
minLines = 3,
maxLines = 3,
)
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
openDialog = false
if (title.isNotEmpty() && text.isNotEmpty() && tagId != null) {
val newArticle = Article(
null, title, text,
Date().time, getUser?.id.toString().toInt(), tagId
)
coroutineScope.launch {
articlePageScreenViewModel.insertArticle(newArticle)
}
}
},
modifier = Modifier
.padding(start = 100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
Text(text = "Add", fontSize = 20.sp)
}
}
}
},
)
}
if (openDialogUser) {
AlertDialog(
onDismissRequest = {
openDialogUser = false
email = getUser?.email.toString()
password = getUser?.password.toString()
nickname = getUser?.nickname.toString()
},
content = {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(16.dp),
) {
Text(
text = "Edit your data",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 20.sp,
),
)
email?.let { ValidateEmail(it, { email = it }) }
password?.let {
OutlinedTextField(
value = it,
onValueChange = {
password = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Password") },
placeholder = { Text("Write password...") },
minLines = 1,
maxLines = 1,
)
}
nickname?.let {
OutlinedTextField(
value = it,
onValueChange = {
nickname = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Nickname") },
placeholder = { Text("Write nickname...") },
minLines = 1,
maxLines = 1,
)
}
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
openDialogUser = false
if (password?.isNotEmpty() == true && email?.isNotEmpty() == true && isValidEmail(
email!!
) && nickname?.isNotEmpty() == true
) {
getUser?.password = password as String
getUser?.email = email as String
getUser?.nickname = nickname as String
coroutineScope.launch {
getUser?.let {
articlePageScreenViewModel.updateUser(it)
}
}
}
},
modifier = Modifier
.padding(start = 100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
Text(text = "Edit", fontSize = 20.sp)
}
}
}
},
)
}
LazyColumn(
contentPadding = PaddingValues(top = 70.dp, bottom = 320.dp),
modifier = modifier
.requiredWidth(width = 412.dp)
.requiredHeight(height = 915.dp)
.background(color = Color.White)
) {
item {
Box(
modifier = Modifier
.offset(
x = 106.dp,
y = 95.dp
)
.requiredWidth(width = 200.dp)
.requiredHeight(height = 310.dp)
) {
Image(
painter = painterResource(id = R.drawable.ixbtcom_colored_logo1),
contentDescription = "Ellipse 8",
modifier = Modifier
.requiredSize(size = 200.dp)
.clip(shape = CircleShape)
)
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 215.dp
)
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xffdbdbf1))
)
Text(
text = "${getUser?.nickname}",
color = Color(0xff423a99),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
)
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 266.dp
)
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xffdbdbf1))
)
Text(
text = "${getUser?.email}",
color = Color(0xff423a99),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
)
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 317.dp
)
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99))
)
Text(
text = "Add Article!",
color = Color(0xffdbdbf1),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
.clickable { openDialog = true })
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 368.dp
)
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99))
)
Text(
text = "Edit my Data",
color = Color(0xffdbdbf1),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
.clickable { openDialogUser = true })
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 419.dp
)
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99))
)
Text(
text = "Get Report",
color = Color(0xffdbdbf1),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 17.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.requiredWidth(width = 200.dp)
.requiredHeight(height = 44.dp)
.wrapContentHeight(align = Alignment.CenterVertically)
.clickable { navController.navigate(BottomBarScreen.Report.route)})
}
}
}
}
}

View File

@ -0,0 +1,280 @@
package com.example.pmulabs.screensMobile
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerColors
import androidx.compose.material3.DisplayMode
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.api.model.ReportRemote
import com.example.pmulabs.designElem.elem.BackButton
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ReportViewModel
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Date
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ReportScreen(navController: NavController?, viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory))
{
val dateStateStart = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val dateStateEnd = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
val coroutineScope = rememberCoroutineScope()
val reportResultPageState = viewModel.reportResultPageUiState
LazyColumn(
contentPadding = PaddingValues(top=75.dp, bottom = 70.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(15.dp),
modifier = Modifier
.background(Color.White)
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
)
{
item{
BackButton(modifier=Modifier
.clickable {
reportResultPageState.resReport= emptyList()
navController?.popBackStack()
})
}
item {
DatePicker(
title = {
Text(
text = "Start Date",
style = MaterialTheme.typography.headlineLarge
)
},
state = dateStateStart,
colors= DatePickerColors(
containerColor = Color.White,
currentYearContentColor=Color(0xff423a99),
dateTextFieldColors = TextFieldDefaults.textFieldColors(
Color(0xff423a99),
containerColor = Color.White,
focusedLabelColor = Color(0xff423a99),
focusedPlaceholderColor = Color(0xff423a99),
focusedTrailingIconColor = Color(0xff423a99)
),
dayContentColor=Color(0xff423a99),
dayInSelectionRangeContainerColor=Color(0xff423a99),
dayInSelectionRangeContentColor=Color(0xff423a99),
disabledDayContentColor=Color(0xff423a99),
disabledSelectedDayContainerColor=Color(0xff423a99),
disabledSelectedDayContentColor=Color(0xff423a99),
disabledSelectedYearContainerColor=Color(0xff423a99),
disabledSelectedYearContentColor=Color(0xff423a99),
disabledYearContentColor=Color(0xff423a99),
dividerColor=Color(0xff423a99),
headlineContentColor=Color(0xff423a99),
navigationContentColor=Color(0xff423a99),
selectedDayContainerColor=Color(0xff423a99),
selectedDayContentColor=Color.White,
selectedYearContainerColor=Color(0xff423a99),
selectedYearContentColor=Color.White,
subheadContentColor=Color(0xff423a99),
titleContentColor=Color(0xff423a99),
todayContentColor=Color(0xff423a99),
todayDateBorderColor=Color(0xff423a99),
weekdayContentColor=Color(0xff423a99),
yearContentColor=Color(0xff423a99)
),
)
val selectedDateStart = dateStateStart.selectedDateMillis
if (selectedDateStart != null) {
val resultDate = selectedDateStart
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = resultDate))
} else {
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = 0))
}
DatePicker(
title ={
Text(
text = "End Date",
style = MaterialTheme.typography.headlineLarge
)},
state = dateStateEnd,
colors= DatePickerColors(
containerColor = Color.White,
currentYearContentColor=Color(0xff423a99),
dateTextFieldColors = TextFieldDefaults.textFieldColors(
Color(0xff423a99),
containerColor = Color.White,
focusedLabelColor = Color(0xff423a99),
focusedPlaceholderColor = Color(0xff423a99),
focusedTrailingIconColor = Color(0xff423a99)
),
dayContentColor=Color(0xff423a99),
dayInSelectionRangeContainerColor=Color(0xff423a99),
dayInSelectionRangeContentColor=Color(0xff423a99),
disabledDayContentColor=Color(0xff423a99),
disabledSelectedDayContainerColor=Color(0xff423a99),
disabledSelectedDayContentColor=Color(0xff423a99),
disabledSelectedYearContainerColor=Color(0xff423a99),
disabledSelectedYearContentColor=Color(0xff423a99),
disabledYearContentColor=Color(0xff423a99),
dividerColor=Color(0xff423a99),
headlineContentColor=Color(0xff423a99),
navigationContentColor=Color(0xff423a99),
selectedDayContainerColor=Color(0xff423a99),
selectedDayContentColor=Color.White,
selectedYearContainerColor=Color(0xff423a99),
selectedYearContentColor=Color.White,
subheadContentColor=Color(0xff423a99),
titleContentColor=Color(0xff423a99),
todayContentColor=Color(0xff423a99),
todayDateBorderColor=Color(0xff423a99),
weekdayContentColor=Color(0xff423a99),
yearContentColor=Color(0xff423a99)
),
)
val selectedDateEnd = dateStateEnd.selectedDateMillis
if (selectedDateEnd != null) {
val resultDate = selectedDateEnd
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = resultDate))
} else {
viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = 0))
}
Spacer(modifier = Modifier.height(16.dp))
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
coroutineScope.launch { viewModel.getReport() }
},
enabled = viewModel.reportPageUiState.isEntryValid,
modifier = Modifier
.fillMaxWidth(0.5f)
.height(50.dp)
) {
Text(text = "Get Report", fontSize = 20.sp)
}
Spacer(modifier = Modifier.height(32.dp))
Text(
color = Color(0xff423a99),
text = "Result",
style = MaterialTheme.typography.headlineLarge
)
TableScreen(reportData = reportResultPageState.resReport)
}
}
}
@Composable
fun ItemCell(
title: String,
commentCount: Int,
category: String,
userName: String,
date: String
) {
Box(
modifier = Modifier
.width(400.dp)
.padding(8.dp)
.clip(shape = RoundedCornerShape(5.dp))
.background(Color(0xffdbdbf1))
) {
Column(Modifier.padding(16.dp)) {
Text(
text = "Заголовок: $title",
color = Color(0xff423a99),
style = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "Кол-во комментариев: $commentCount",
color = Color(0xff423a99),
style = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "Категория: $category",
color = Color(0xff423a99),
style = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "Автор: $userName",
color = Color(0xff423a99),
style = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
Text(
text = "Дата публикации: $date",
color = Color(0xff423a99),
style = TextStyle(fontSize = 14.sp, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(bottom = 8.dp)
)
}
}
}
@Composable
fun TableScreen(reportData: List<ReportRemote>) {
val column1Weight = .8f // 30%
val column2Weight = 1f // 30%
val formatter = SimpleDateFormat("dd-MMMM-YY")
Column(
Modifier
.padding(16.dp)) {
// Here are all the lines of your table.
reportData.forEach {
val (title, publishDate, tagName, userName,comments) = it
Row(Modifier.fillMaxWidth()) {
ItemCell(
title,
comments,
tagName,
userName,
formatter.format(Date(publishDate)).toString()
)
}
}
}
}

View File

@ -0,0 +1,207 @@
package com.example.pmulabs.screensMobile
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.designElem.items.TagItem
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.TagItemViewModel
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TagsScreen(navController: NavController, modifier: Modifier = Modifier, tagItemViewModel: TagItemViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
var tags = tagItemViewModel.tags.collectAsLazyPagingItems()
var getUser by remember { mutableStateOf(currentUserViewModel.user) }
val coroutineScope = rememberCoroutineScope()
var openDialog by remember { mutableStateOf(false) }
var text by remember { mutableStateOf("") }
if (openDialog) {
AlertDialog(
onDismissRequest = {
openDialog = false
text = ""
},
content = {
Column(
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.background(Color.White)
.padding(16.dp),
) {
Text(
text = "Add your tag",
color = Color(0xff423a99),
style = TextStyle(
fontSize = 20.sp,
),
)
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White,
focusedIndicatorColor = Color(0xff423a99),
focusedLabelColor = Color(0xff423a99),
unfocusedIndicatorColor = Color(0xff423a99),
unfocusedLabelColor = Color(0xff423a99)
),
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(10.dp),
label = { Text("Tag") },
placeholder = { Text("Write tag...") },
minLines = 3,
maxLines = 3,
)
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
openDialog = false
if(text.isNotEmpty()) {
val newTag = Tag(text, null, getUser?.id.toString().toInt())
coroutineScope.launch {
tagItemViewModel.insertTag(newTag)
}
navController.navigate(BottomBarScreen.Categories.route)
}
},
modifier = Modifier
.padding(start = 100.dp)
.fillMaxWidth(0.5f)
.height(40.dp)
) {
Text(text = "Add", fontSize = 20.sp)
}
}
}
},
)
}
LazyColumn(
modifier= Modifier
.background(Color.White)
.fillMaxSize(),
contentPadding = PaddingValues(top =75.dp, bottom = 10.dp, start = 25.dp,end=25.dp),
verticalArrangement = Arrangement.spacedBy(15.dp)){
item{
Box(
modifier = modifier
.requiredWidth(width = 305.dp)
.requiredHeight(height = 58.dp)
) {
Box(
modifier = Modifier
.requiredWidth(width = 305.dp)
.requiredHeight(height = 58.dp)
.clip(shape = RoundedCornerShape(15.dp))
.background(color = Color(0xff423a99))
.clickable { openDialog=true }
)
Text(
text = "Add Tag!",
color = Color(0xFFFFFFFF),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Bold
),
modifier = Modifier
.offset(
x = 105.dp,
y = 14.dp
)
)
}
}
items(count = tags.itemCount) { index ->
val tag = tags[index]
if (tag != null) {
TagItem(
navController=navController,
teg = tag,
viewModel = currentUserViewModel,
onTagClick = {navController.navigate(BottomBarScreen.SearchByTag.passId(tag?.id.toString()))}
)
}
}
tags.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.refresh is LoadState.Error -> {
val err = tags.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = tags.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}

View File

@ -0,0 +1,240 @@
package com.example.pmulabs.screensMobile.authScreens
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.designElem.elem.ValidateEmail
import com.example.pmulabs.designElem.elem.isValidEmail
import com.example.pmulabs.designElem.statDesign.LeftCircles
import com.example.pmulabs.designElem.statDesign.LogoMobile
import com.example.pmulabs.designElem.statDesign.RightCircles
import com.example.pmulabs.graphs.AuthScreen
import com.example.pmulabs.graphs.Graph
import com.example.pmulabs.room.models.User
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.EntryScreenViewModel
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EntryScreen(navController: NavController, modifier: Modifier = Modifier,entryScreenViewModel: EntryScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var emailValue by rememberSaveable { mutableStateOf("") }
var passwordValue by rememberSaveable { mutableStateOf("") }
entryScreenViewModel.setUserList()
val users = mutableStateOf<List<User>>(entryScreenViewModel.userList)
val argument = currentUserViewModel.argument.value
var passwordVisibility by rememberSaveable { mutableStateOf(false) }
val emailRegex = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
val coroutineScope = rememberCoroutineScope()
Box(
modifier = modifier
.requiredWidth(width = 412.dp)
.requiredHeight(height = 915.dp)
.background(color = Color.White)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 128.dp,
y = 730.dp
)
.requiredWidth(width = 381.dp)
.requiredHeight(height = 268.dp)
.rotate(degrees = 8.33f)
) {
RightCircles()
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = (-111.28436279296875).dp,
y = (-96.86965942382812).dp
)
.requiredWidth(width = 250.dp)
.requiredHeight(height = 290.dp)
.rotate(degrees = 12.96f)
) {
LeftCircles()
}
}
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.8f)
.clip(RoundedCornerShape(15.dp))
.background(Color.Transparent)
.padding(10.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.Transparent), contentAlignment = Alignment.TopCenter
) {
LogoMobile(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.fillMaxWidth()
.requiredHeight(height = 131.dp)
)
}
Text(
text = "Please, Sign In to your account",
color = Color(0xff7d7dbd),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 20.sp
),
modifier = modifier
.requiredWidth(width = 436.dp)
.requiredHeight(height = 27.dp)
)
Spacer(modifier = Modifier.padding(10.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
ValidateEmail(emailValue, {emailValue=it} )
Spacer(modifier = Modifier.padding(3.dp))
TextField(
value = passwordValue,
onValueChange = { passwordValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White
),
modifier = modifier
.requiredWidth(width = 269.dp)
.requiredHeight(height = 53.dp)
.shadow(shape = RoundedCornerShape(40.dp), elevation = 5.dp)
.clip(shape = RoundedCornerShape(40.dp)),
label = { Text("Password") },
singleLine = true,
placeholder = { Text("Password") },
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisibility)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
val description =
if (passwordVisibility) "Hide password" else "Show password"
IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
Icon(imageVector = image, description)
}
}
)
Spacer(modifier = Modifier.padding(10.dp))
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
if (passwordValue.isNotEmpty() && isValidEmail(emailValue)) {
users.value.forEach { user ->
if (user.password == passwordValue && user.email == emailValue) {
currentUserViewModel.setArgument(user.id.toString())
navController.navigate(route = Graph.passUserId(user.id.toString())) {
}
}
}
}
},
modifier = Modifier
.fillMaxWidth(0.5f)
.height(50.dp)
) {
Text(text = "Sign In", fontSize = 20.sp)
}
Spacer(modifier = Modifier.padding(3.dp))
Text(
text = "Don't have an account yet? Sign Up",
color = Color(0xff7d7dbd),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 20.sp
),
modifier = Modifier
.fillMaxSize()
.offset(
x = 0.dp,
y = 55.dp
)
.clickable {
navController.navigate(route = AuthScreen.Register.route)
{
popUpTo(AuthScreen.Splash.route)
}
}
)
Spacer(modifier = Modifier.padding(20.dp))
}
}
}
}
}

View File

@ -0,0 +1,289 @@
package com.example.pmulabs.screensMobile.authScreens
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.pmulabs.designElem.elem.ValidateEmail
import com.example.pmulabs.designElem.elem.isValidEmail
import com.example.pmulabs.designElem.statDesign.LeftCircles
import com.example.pmulabs.designElem.statDesign.LogoMobile
import com.example.pmulabs.designElem.statDesign.RightCircles
import com.example.pmulabs.graphs.AuthScreen
import com.example.pmulabs.graphs.Graph
import com.example.pmulabs.room.models.User
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.CurrentUserViewModel
import com.example.pmulabs.viewModels.RegisterScreenViewModel
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RegisterScreen(navController: NavController, modifier: Modifier = Modifier,registerScreenViewModel: RegisterScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
var emailValue by rememberSaveable { mutableStateOf("") }
var passwordValue by rememberSaveable { mutableStateOf("") }
var nicknameValue by rememberSaveable { mutableStateOf("") }
val coroutineScope = rememberCoroutineScope()
registerScreenViewModel.setUserList()
var users = mutableStateOf<List<User>>(emptyList())
registerScreenViewModel.users.observeForever { userList ->
users.value = userList
}
var passwordVisibility by rememberSaveable { mutableStateOf(false) }
val emailRegex = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex()
Box(
modifier = modifier
.requiredWidth(width = 412.dp)
.requiredHeight(height = 915.dp)
.background(color = Color.White)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 128.dp,
y = 730.dp
)
.requiredWidth(width = 381.dp)
.requiredHeight(height = 268.dp)
.rotate(degrees = 8.33f)
) {
RightCircles()
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = (-111.28436279296875).dp,
y = (-96.86965942382812).dp
)
.requiredWidth(width = 250.dp)
.requiredHeight(height = 290.dp)
.rotate(degrees = 12.96f)
) {
LeftCircles()
}
}
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomCenter) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.8f)
.clip(RoundedCornerShape(15.dp))
.background(Color.Transparent)
.padding(10.dp)
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.Transparent), contentAlignment = Alignment.TopCenter
) {
LogoMobile(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.fillMaxWidth()
.requiredHeight(height = 131.dp)
)
}
Text(
text = "Please, Sign In to your account",
color = Color(0xff7d7dbd),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 20.sp
),
modifier = modifier
.requiredWidth(width = 436.dp)
.requiredHeight(height = 27.dp)
)
Spacer(modifier = Modifier.padding(10.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
TextField(
value = nicknameValue,
onValueChange = {
nicknameValue = it
},
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White
),
modifier = Modifier
.requiredWidth(width = 269.dp)
.requiredHeight(height = 53.dp)
.shadow(shape = RoundedCornerShape(40.dp), elevation = 5.dp)
.clip(shape = RoundedCornerShape(40.dp)),
label = { Text("Nickname") },
placeholder = { Text("Nickname") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
singleLine = true,
)
Spacer(modifier = Modifier.padding(3.dp))
ValidateEmail(emailValue, { emailValue = it })
Spacer(modifier = Modifier.padding(3.dp))
TextField(
value = passwordValue,
onValueChange = { passwordValue = it },
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.White
),
modifier = modifier
.requiredWidth(width = 269.dp)
.requiredHeight(height = 53.dp)
.shadow(shape = RoundedCornerShape(40.dp), elevation = 5.dp)
.clip(shape = RoundedCornerShape(40.dp)),
label = { Text("Password") },
singleLine = true,
placeholder = { Text("Password") },
visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
trailingIcon = {
val image = if (passwordVisibility)
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
val description =
if (passwordVisibility) "Hide password" else "Show password"
IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
Icon(imageVector = image, description)
}
}
)
Spacer(modifier = Modifier.padding(10.dp))
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {
var isExist = false;
if (passwordValue.isNotEmpty() && isValidEmail(emailValue) && nicknameValue.isNotEmpty()) {
users.value.forEach { user ->
if (user.email == emailValue || user.nickname == nicknameValue) {
Log.d("User already exist. User id: ", user.id.toString())
isExist = true
}
}
if (!isExist) {
val newUser = User(null, nicknameValue, emailValue, passwordValue, "user")
coroutineScope.launch {
val insertResult = async {
registerScreenViewModel.insertUser(newUser)
}
insertResult.await()
registerScreenViewModel.setUserList()
registerScreenViewModel.users.observeForever { userList ->
users.value = userList
Log.println(Log.ASSERT, "UsersList", users.value.toString())
users.value?.forEach { user ->
if (user.password == passwordValue && user.email == emailValue) {
currentUserViewModel.setArgument(user.id.toString())
navController.navigate(route = Graph.passUserId(user.id.toString()))
}
}
}
}
}
}
},
modifier = Modifier
.fillMaxWidth(0.5f)
.height(50.dp)
) {
Text(text = "Sign Up", fontSize = 20.sp)
}
Spacer(modifier = Modifier.padding(0.dp))
Text(
text = "Do you have an account? Sign In",
color = Color(0xff7d7dbd),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 20.sp
),
modifier = Modifier
.fillMaxSize()
.offset(
x = 0.dp,
y = 15.dp
)
.clickable {
navController.navigate(route = AuthScreen.Entry.route)
{
popUpTo(AuthScreen.Splash.route)
}
}
)
Spacer(modifier = Modifier.padding(20.dp))
}
}
}
}
}

View File

@ -0,0 +1,143 @@
package com.example.pmulabs.screensMobile.authScreens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.pmulabs.designElem.statDesign.LeftCircles
import com.example.pmulabs.designElem.statDesign.LogoMobile
import com.example.pmulabs.designElem.statDesign.RightCircles
import com.example.pmulabs.graphs.AuthScreen
@Composable
fun SplashScreen(navController: NavController,modifier: Modifier = Modifier) {
Box(
modifier = modifier
.requiredWidth(width = 412.dp)
.requiredHeight(height = 915.dp)
.background(color = Color.White)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 128.dp,
y = 730.dp
)
.requiredWidth(width = 381.dp)
.requiredHeight(height = 268.dp)
.rotate(degrees = 8.33f)
) {
RightCircles()
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = (-111.28436279296875).dp,
y = (-96.86965942382812).dp
)
.requiredWidth(width = 250.dp)
.requiredHeight(height = 290.dp)
.rotate(degrees = 12.96f)
) {
LeftCircles()
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 200.dp
)
.requiredWidth(width = 412.dp)
.requiredHeight(height = 190.dp)
) {
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 62.00006103515625.dp,
y = 50.dp
)
.requiredWidth(width = 302.dp)
.requiredHeight(height = 140.dp)
) {
LogoMobile(modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 9.dp
)
.fillMaxWidth()
.requiredHeight(height = 131.dp))
}
Text(
text = "Welcome to",
color = Color(0xff7d7dbd),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 48.sp,
fontWeight = FontWeight.Bold),
modifier = Modifier
.fillMaxSize())
}
Box(
modifier = Modifier
.align(alignment = Alignment.TopStart)
.offset(
x = 0.dp,
y = 649.2312622070312.dp
)
.requiredWidth(width = 412.dp)
.requiredHeight(height = 92.dp)
) {
Text(
text = "Best app with game news!",
color = Color(0xff7d7dbd),
textAlign = TextAlign.Center,
style = TextStyle(
fontSize = 20.sp),
modifier = Modifier
.fillMaxSize())
Button(
colors = ButtonColors(
containerColor = Color(0xff423a99),
disabledContainerColor = Color(0xff423a99),
contentColor = Color.White,
disabledContentColor = Color.White
),
onClick = {navController.navigate(route = AuthScreen.Entry.route)},
modifier = Modifier
.fillMaxWidth(0.5f)
.height(50.dp)
.align(alignment = Alignment.TopStart)
.offset(
x = 106.5.dp,
y = 31.31878662109375.dp
)
) {
Text(text = "Get Started", fontSize = 20.sp)
}
}
}
}

View File

@ -0,0 +1,88 @@
package com.example.pmulabs.screensMobile.filterScreens
import android.annotation.SuppressLint
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.basecomponents.navigate.CALENDAR_ARGUMENT_DATE
import com.example.pmulabs.designElem.elem.BackButton
import com.example.pmulabs.designElem.items.ArticleItem
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
import java.text.SimpleDateFormat
@SuppressLint("UnrememberedMutableState")
@Composable
fun CalendarScreen(navController: NavController, articleScreenViewModel: ArticleScreenViewModel= viewModel(factory = AppViewModelProvider.Factory), modifier: Modifier = Modifier, currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val articles = articleScreenViewModel.articles.collectAsLazyPagingItems()
var date=navController.currentBackStackEntry?.arguments?.getString(CALENDAR_ARGUMENT_DATE).toString()
Log.d("date: ",date)
val formatter = SimpleDateFormat("dd-MMMM-YY")
LazyColumn(
modifier= Modifier
.background(Color.White)
.fillMaxSize(),
contentPadding = PaddingValues(top =75.dp, bottom = 10.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)){
item{
BackButton(modifier=Modifier
.clickable { navController.popBackStack()})
}
items(count = articles.itemCount) { index ->
val article = articles[index]
val publishDate=formatter.format(article?.publishDate)
if(publishDate==date) {
if (article != null) {
ArticleItem(
navController=navController,
article = article,
currentUserViewModel = currentUserViewModel,
onArticleClick = {
navController.navigate(
BottomBarScreen.ArticlePage.passId(
article?.id.toString()
)
)
}
)
}
}
}
articles.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.refresh is LoadState.Error -> {
val err = articles.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = articles.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}

View File

@ -0,0 +1,71 @@
package com.example.pmulabs.screensMobile.filterScreens
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.basecomponents.navigate.SEARCHBYTAG_ARGUMENT_KEY
import com.example.pmulabs.designElem.elem.BackButton
import com.example.pmulabs.designElem.items.ArticleItem
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
@Composable
fun SearchByTagScreen(navController: NavController, modifier: Modifier = Modifier, articleScreenViewModel: ArticleScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val articles = articleScreenViewModel.articles.collectAsLazyPagingItems()
var id=navController.currentBackStackEntry?.arguments?.getString(SEARCHBYTAG_ARGUMENT_KEY).toString()
Log.d("id: ",id)
LazyColumn(modifier= Modifier
.background(Color.White)
.fillMaxSize(),
contentPadding = PaddingValues(top =75.dp, bottom = 10.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)){
item{
BackButton(modifier=Modifier
.clickable { navController.popBackStack()})
}
items(count = articles.itemCount) { index ->
val article = articles[index]
if(article?.tagId.toString()==id) {
if (article != null) {
ArticleItem(navController=navController,article = article, currentUserViewModel = currentUserViewModel, onArticleClick = {navController.navigate(BottomBarScreen.ArticlePage.passId(article.id.toString()))})
}
}
}
articles.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.refresh is LoadState.Error -> {
val err = articles.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = articles.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}

View File

@ -0,0 +1,71 @@
package com.example.pmulabs.screensMobile.filterScreens
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.example.pmulabs.basecomponents.navigate.BottomBarScreen
import com.example.pmulabs.basecomponents.navigate.SEARCH_ARGUMENT_TEXT
import com.example.pmulabs.designElem.elem.BackButton
import com.example.pmulabs.designElem.items.ArticleItem
import com.example.pmulabs.viewModels.AppViewModelProvider
import com.example.pmulabs.viewModels.ArticleScreenViewModel
import com.example.pmulabs.viewModels.CurrentUserViewModel
@Composable
fun SearchScreen(navController: NavController, modifier: Modifier = Modifier, articleScreenViewModel: ArticleScreenViewModel = viewModel(factory = AppViewModelProvider.Factory), currentUserViewModel: CurrentUserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val articles = articleScreenViewModel.articles.collectAsLazyPagingItems()
var text=navController.currentBackStackEntry?.arguments?.getString(SEARCH_ARGUMENT_TEXT).toString()
Log.d("text: ",text)
LazyColumn(
modifier= Modifier
.background(Color.White)
.fillMaxSize(),
contentPadding = PaddingValues(top =75.dp, bottom = 10.dp, start = 10.dp,end=10.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)){
item{
BackButton(modifier=Modifier
.clickable { navController.popBackStack()})
}
items(count = articles.itemCount) { index ->
val article = articles[index]
if(article?.title?.contains(text)==true) {
if (article != null) {
ArticleItem(navController=navController,article = article,currentUserViewModel=currentUserViewModel,onArticleClick = {navController.navigate(BottomBarScreen.ArticlePage.passId(article.id.toString()))})
}
}
}
articles.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.append is LoadState.Loading -> {
item { CircularProgressIndicator(modifier = Modifier.fillParentMaxSize(), color = Color(0xff423a99)) }
}
loadState.refresh is LoadState.Error -> {
val err = articles.loadState.refresh as LoadState.Error
item { Text(err.error.localizedMessage) }
}
loadState.append is LoadState.Error -> {
val err = articles.loadState.append as LoadState.Error
item { Text(err.error.localizedMessage) }
}
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.example.pmulabs.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -0,0 +1,70 @@
package com.example.pmulabs.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun PMULabsTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@ -0,0 +1,34 @@
package com.example.pmulabs.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View File

@ -0,0 +1,42 @@
package com.example.pmulabs.viewModels
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.pmulabs.NewsPortalApplication
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
CurrentUserViewModel(newsPortalApplication().container.userRestRepository)
}
initializer {
TagItemViewModel(newsPortalApplication().container.tagRestRepository)
}
initializer {
CommentItemViewModel(newsPortalApplication().container.userRestRepository,newsPortalApplication().container.commentRestRepository)
}
initializer {
ArticleItemViewModel(newsPortalApplication().container.tagRestRepository,newsPortalApplication().container.commentRestRepository,newsPortalApplication().container.articleRestRepository)
}
initializer {
EntryScreenViewModel(newsPortalApplication().container.userRestRepository)
}
initializer {
RegisterScreenViewModel(newsPortalApplication().container.userRestRepository)
}
initializer {
ArticleScreenViewModel(newsPortalApplication().container.articleRestRepository)
}
initializer {
ArticlePageScreenViewModel(newsPortalApplication().container.tagRestRepository,newsPortalApplication().container.commentRestRepository,newsPortalApplication().container.articleRestRepository,newsPortalApplication().container.userRestRepository)
}
initializer {
ReportViewModel(newsPortalApplication().container.userRestRepository)
}
}
}
fun CreationExtras.newsPortalApplication(): NewsPortalApplication =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as NewsPortalApplication)

View File

@ -0,0 +1,45 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.TagRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ArticleItemViewModel(
private val tagRepository: TagRepository,
private val commentRepository: CommentRepository,
private val articleRepository: ArticleRepository
) : ViewModel() {
var tagList by mutableStateOf<List<Tag>>(emptyList())
fun setTagList() {
viewModelScope.launch(Dispatchers.IO) {
tagList=tagRepository.getAllTags()
}
}
suspend fun updateArticle(article: Article) {
articleRepository.updateArticle(article)
}
suspend fun deleteArticle(article: Article) {
articleRepository.deleteArticle(article)
}
suspend fun getTagByName(name: String) : Tag? {
return tagRepository.getTagByName(name)
}
suspend fun getCount(articleId: Int?): Int? = withContext(Dispatchers.IO) {
commentRepository.getCountComment(articleId)
}
}

View File

@ -0,0 +1,84 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.ArticleRepository
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.TagRepository
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class ArticlePageScreenViewModel(
private val tagRepository: TagRepository,
private val commentRepository: CommentRepository,
private val articleRepository: ArticleRepository,
private val userRepository: UserRepository
) : ViewModel() {
private val articleid = mutableStateOf<Int?>(null)
private val tagid = mutableStateOf<Int?>(null)
var article by mutableStateOf<Article?>(null)
var tag by mutableStateOf<Tag?>(null)
val comments: Flow<PagingData<Comment>> = commentRepository.getComments()
var getComments by mutableStateOf<List<Comment>>(emptyList())
fun setCommentList() {
viewModelScope.launch(Dispatchers.IO) {
getComments=commentRepository.getAllComments()
}
}
fun getArticleById(articleId:Int) {
articleid.value = articleId
viewModelScope.launch {
article = articleRepository.getArticleById(articleid.value!!)
}
}
var userList by mutableStateOf<List<User>>(emptyList())
fun setUserList() {
viewModelScope.launch(Dispatchers.IO) {
userList=userRepository.getAllUsers()
}
}
var tagList by mutableStateOf<List<Tag>>(emptyList())
fun setTagList() {
viewModelScope.launch(Dispatchers.IO) {
tagList=tagRepository.getAllTags()
}
}
suspend fun insertComment(comment: Comment) {
commentRepository.insertComment(comment)
}
suspend fun updateUser(user: User) {
userRepository.updateUser(user)
}
suspend fun insertArticle(article: Article) {
articleRepository.insertArticle(article)
}
suspend fun getTagByName(name: String) : Tag? {
return tagRepository.getTagByName(name)
}
fun getTagById(tagId:Int) {
tagid.value = tagId
viewModelScope.launch {
tag = tagRepository.getTagById(tagid.value!!)
}
}
}

View File

@ -0,0 +1,24 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Article
import com.example.pmulabs.room.repository.ArticleRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class ArticleScreenViewModel(private val articleRepository: ArticleRepository) : ViewModel() {
val articles: Flow<PagingData<Article>> = articleRepository.getArticles()
var getArticles by mutableStateOf<List<Article>>(emptyList())
fun setArticleList() {
viewModelScope.launch(Dispatchers.IO) {
getArticles=articleRepository.getAllArticles()
}
}
}

View File

@ -0,0 +1,36 @@
package com.example.pmulabs.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.Comment
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.CommentRepository
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CommentItemViewModel(
private val userRepository: UserRepository,
private val commentRepository: CommentRepository
) : ViewModel() {
suspend fun updateComment(comment: Comment) {
commentRepository.updateComment(comment)
}
var user : User? = null
suspend fun getCount(articleId: Int): Int? = withContext(Dispatchers.IO) {
commentRepository.getCountComment(articleId)
}
fun getUser(commId: Int) {
viewModelScope.launch(Dispatchers.IO) {
user=userRepository.getUserById(commId)
}
}
suspend fun deleteComment(comment: Comment) {
commentRepository.deleteComment(comment)
}
}

View File

@ -0,0 +1,24 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.launch
class CurrentUserViewModel(private val userRepository: UserRepository) : ViewModel(){
val argument = mutableStateOf<String?>(null)
private val userid = mutableStateOf<Int?>(null)
var user by mutableStateOf<User?>(null)
fun setArgument(arg: String) {
argument.value = arg
userid.value = arg.toInt()
viewModelScope.launch {
user = userRepository.getUserById(userid.value)
}
}
}

View File

@ -0,0 +1,20 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.launch
class EntryScreenViewModel(private val userRepository: UserRepository) : ViewModel() {
var userList by mutableStateOf<List<User>>(emptyList())
fun setUserList() {
viewModelScope.launch {
userList=userRepository.getAllUsers()
}
}
}

View File

@ -0,0 +1,25 @@
package com.example.pmulabs.viewModels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.pmulabs.room.models.User
import com.example.pmulabs.room.repository.UserRepository
import kotlinx.coroutines.launch
class RegisterScreenViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> get() = _users
fun setUserList() {
viewModelScope.launch {
_users.value = userRepository.getAllUsers()
}
}
suspend fun insertUser(user: User) {
userRepository.insertUser(user)
}
}

View File

@ -0,0 +1,47 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.example.pmulabs.api.model.ReportRemote
import com.example.pmulabs.room.repository.UserRepository
class ReportViewModel(
private val userRepository: UserRepository
) : ViewModel() {
var reportPageUiState by mutableStateOf(ReportPageUiState())
private set
var reportResultPageUiState by mutableStateOf(ReportResultPageUiState())
private set
fun onUpdate(reportDetails: ReportDetails)
{
reportPageUiState = ReportPageUiState(reportDetails = reportDetails,isEntryValid = validateInput(reportDetails))
}
private fun validateInput(uiState: ReportDetails = reportPageUiState.reportDetails): Boolean {
return with(uiState) {
startDate >=0
&& endDate >=0
&& startDate < endDate
}
}
suspend fun getReport(){
val res = userRepository.getReport(reportPageUiState.reportDetails.startDate, reportPageUiState.reportDetails.endDate)
reportResultPageUiState = ReportResultPageUiState(res)
}
}
data class ReportDetails(
val startDate: Long = 0,
val endDate: Long = 0
)
data class ReportPageUiState(
val reportDetails: ReportDetails = ReportDetails(),
val isEntryValid: Boolean = false
)
data class ReportResultPageUiState(
var resReport:List<ReportRemote> = emptyList()
)

View File

@ -0,0 +1,28 @@
package com.example.pmulabs.viewModels
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class SearchViewModel : ViewModel() {
private val _searchWidgetState: MutableState<SearchWidget> =
mutableStateOf(value = SearchWidget.CLOSED)
val searchWidgetState: State<SearchWidget> = _searchWidgetState
private val _searchTextState: MutableState<String> =
mutableStateOf(value = "")
val searchTextState: State<String> = _searchTextState
fun updateSearchWidgetState(newValue: SearchWidget) {
_searchWidgetState.value = newValue
}
fun updateSearchTextState(newValue: String) {
_searchTextState.value = newValue
}
}

View File

@ -0,0 +1,6 @@
package com.example.pmulabs.viewModels
enum class SearchWidget {
OPENED,
CLOSED
}

View File

@ -0,0 +1,24 @@
package com.example.pmulabs.viewModels
import androidx.lifecycle.ViewModel
import androidx.paging.PagingData
import com.example.pmulabs.room.models.Tag
import com.example.pmulabs.room.repository.TagRepository
import kotlinx.coroutines.flow.Flow
class TagItemViewModel(
private val tagRepository: TagRepository
) : ViewModel() {
var tags: Flow<PagingData<Tag>> = tagRepository.getTags()
suspend fun insertTag(tag: Tag) {
tagRepository.insertTag(tag)
}
suspend fun updateTag(tag: Tag) {
tagRepository.updateTag(tag)
}
suspend fun deleteTag(tag: Tag) {
tagRepository.deleteTag(tag)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#423A99" 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="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19L3,19L3,5h18v14zM5,10h9v2L5,12zM5,7h9v2L5,9z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" 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="M21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM12,11L3,11L3,9h9v2zM12,7L3,7L3,5h9v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#423A99"
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,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12.31,11.14c-1.77,-0.45 -2.34,-0.94 -2.34,-1.67 0,-0.84 0.79,-1.43 2.1,-1.43 1.38,0 1.9,0.66 1.94,1.64h1.71c-0.05,-1.34 -0.87,-2.57 -2.49,-2.97L13.23,5L10.9,5v1.69c-1.51,0.32 -2.72,1.3 -2.72,2.81 0,1.79 1.49,2.69 3.66,3.21 1.95,0.46 2.34,1.15 2.34,1.87 0,0.53 -0.39,1.39 -2.1,1.39 -1.6,0 -2.23,-0.72 -2.32,-1.64L8.04,14.33c0.1,1.7 1.36,2.66 2.86,2.97L10.9,19h2.34v-1.67c1.52,-0.29 2.72,-1.16 2.73,-2.77 -0.01,-2.2 -1.9,-2.96 -3.66,-3.42z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
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,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13.41,18.09L13.41,20h-2.67v-1.93c-1.71,-0.36 -3.16,-1.46 -3.27,-3.4h1.96c0.1,1.05 0.82,1.87 2.65,1.87 1.96,0 2.4,-0.98 2.4,-1.59 0,-0.83 -0.44,-1.61 -2.67,-2.14 -2.48,-0.6 -4.18,-1.62 -4.18,-3.67 0,-1.72 1.39,-2.84 3.11,-3.21L10.74,4h2.67v1.95c1.86,0.45 2.79,1.86 2.85,3.39L14.3,9.34c-0.05,-1.11 -0.64,-1.87 -2.22,-1.87 -1.5,0 -2.4,0.68 -2.4,1.64 0,0.84 0.65,1.39 2.67,1.91s4.18,1.39 4.18,3.91c-0.01,1.83 -1.38,2.83 -3.12,3.16z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#423A99"
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="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

Some files were not shown because too many files have changed in this diff Show More