feature: coursework completed

This commit is contained in:
Danil Markov 2023-12-29 18:42:15 +04:00
parent b58bbf4421
commit 35a3213edd
54 changed files with 2193 additions and 798 deletions

View File

@ -9,20 +9,68 @@
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7c94e195-a540-483e-9a1c-11797aeb1741" name="Changes" comment="feature: lab5 done">
<list default="true" id="7c94e195-a540-483e-9a1c-11797aeb1741" name="Changes" comment="feature: add logo">
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/ApiStatus.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/RemoteConverters.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/ReportRemote.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/ServiceWithCount.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/repository/RestReportRepository.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/repository/ReportRepository.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/MyViewModel.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/ReportViewModel.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/ChangeService.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/NetworkUI/Error.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/NetworkUI/Loading.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/ServiceReportCard.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Report.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ReportDatePick.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ServiceReportCard.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/UIComponents/DatePicker.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Converters.kt" afterDir="false" />
<change afterPath="$PROJECT_DIR$/app/src/main/res/drawable/profile.png" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/MainActivity.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/MainActivity.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/ServerService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/ServerService.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/ServiceRemote.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/ServiceRemote.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/model/UserRemote.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/repository/RestServiceRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/repository/RestServiceRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/repository/RestUserRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/repository/RestUserRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/repository/ServiceRepository.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/repository/ServiceRepository.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/AppViewModelProvider.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/AppViewModelProvider.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/BasketViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/BasketViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/OrderViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/OrderViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/ServiceViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/ServiceViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/UserViewModel.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/UserViewModel.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/Basket.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/Basket.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/BasketItemUI.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Basket/BasketItemUI.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/AddService.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/AddService.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/ListOfServices.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/ListOfServices.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/Service.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/List_of_Services/Service.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavBar.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavBar.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavController.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavController.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavItem.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavItem.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/OrderItem.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/OrderItem.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/Orders.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Orders/Orders.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Login.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Login.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Profile.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Profile.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ProfileChange.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/ProfileChange.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Registration.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Profile/Registration.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/UIComponents/MyTextField.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/UIComponents/MyTextField.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/AppDatabase.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/AppDatabase.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/dao/ServiceDao.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/dao/ServiceDao.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/repository/ServiceRepositoryImpl.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/database/repository/ServiceRepositoryImpl.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/di/AppContainer.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/di/AppContainer.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/di/AppDataContainer.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/di/AppDataContainer.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Service.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/Service.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/User.kt" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/java/com/example/myapplication/model/User.kt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/app/src/main/res/values/strings.xml" beforeDir="false" afterPath="$PROJECT_DIR$/app/src/main/res/values/strings.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[6681ed71]" />
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[C:\Users\Danil\.android\avd\Pixel_3a_API_34_extension_level_7_x86_64.avd]" />
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$">
<ProjectState />
@ -46,7 +94,7 @@
<component name="Git.Settings">
<option name="RECENT_BRANCH_BY_REPOSITORY">
<map>
<entry key="$PROJECT_DIR$" value="LabWork04" />
<entry key="$PROJECT_DIR$" value="LabWork05" />
</map>
</option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@ -72,12 +120,12 @@
&quot;RunOnceActivity.cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;ToolWindowLogcat.ShowToolbar&quot;: &quot;false&quot;,
&quot;cidr.known.project.marker&quot;: &quot;true&quot;,
&quot;com.android.tools.idea.devicemanager.tab&quot;: &quot;Physical&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/Danil/Desktop/Новая папка&quot;,
&quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
&quot;com.google.services.firebase.aqiPopupShown&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;C:/Users/Danil/Desktop/MDP/labs/app/src/main/java/com/example/myapplication/composeui/Orders&quot;,
&quot;project.structure.last.edited&quot;: &quot;Project&quot;,
&quot;project.structure.proportion&quot;: &quot;0.17&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;emulator&quot;
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.lookFeel&quot;
},
&quot;keyToStringList&quot;: {
&quot;ExportApk.BuildVariants&quot;: [
@ -93,7 +141,7 @@
}
}</component>
<component name="PsdUISettings">
<option name="MODULE_TAB" value="Signing Configs" />
<option name="MODULE_TAB" value="Properties" />
<option name="LAST_EDITED_SIGNING_CONFIG" value="debug" />
<option name="LAST_EDITED_BUILD_TYPE" value="release" />
</component>
@ -101,6 +149,10 @@
<key name="android.template.-1413981578">
<recent name="com.example.myapplication.Navbar" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Users\Danil\Desktop\MDP\labs\app\src\main\java\com\example\myapplication\composeui\Orders" />
<recent name="C:\Users\Danil\Desktop\MDP\labs\app\src\main\res\drawable" />
</key>
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Users\Danil\Desktop\MDP\labs\app\src\main\java\com\example\myapplication\business_logic" />
<recent name="C:\Users\Danil\Desktop\MDP\labs\app\src\main\res\drawable" />
@ -338,7 +390,14 @@
<option name="project" value="LOCAL" />
<updated>1703256177985</updated>
</task>
<option name="localTasksCounter" value="11" />
<task id="LOCAL-00011" summary="feature: add logo">
<created>1703337054935</created>
<option name="number" value="00011" />
<option name="presentableId" value="LOCAL-00011" />
<option name="project" value="LOCAL" />
<updated>1703337054935</updated>
</task>
<option name="localTasksCounter" value="12" />
<servers />
</component>
<component name="Vcs.Log.Tabs.Properties">
@ -361,25 +420,16 @@
<MESSAGE value="feature: lab4 maybe done" />
<MESSAGE value="feature: lab5 almost done, save commit" />
<MESSAGE value="feature: lab5 done" />
<option name="LAST_COMMIT_MESSAGE" value="feature: lab5 done" />
<MESSAGE value="feature: add logo" />
<option name="LAST_COMMIT_MESSAGE" value="feature: add logo" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/composeui/Navbar/NavController.kt</url>
<line>63</line>
<option name="timeStamp" value="23" />
</line-breakpoint>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/BasketViewModel.kt</url>
<line>131</line>
<option name="timeStamp" value="52" />
</line-breakpoint>
<line-breakpoint enabled="true" type="kotlin-line">
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/api/ServiceRemoteMediator.kt</url>
<line>55</line>
<option name="timeStamp" value="53" />
<url>file://$PROJECT_DIR$/app/src/main/java/com/example/myapplication/businessLogic/viewmodel/UserViewModel.kt</url>
<line>49</line>
<option name="timeStamp" value="105" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>

View File

@ -15,10 +15,10 @@ import com.example.myapplication.ui.theme.BlueMain
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//this.deleteDatabase("my-db")
setContent {
AppTheme (darkTheme = false){
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier
.fillMaxSize()

View File

@ -0,0 +1,3 @@
package com.example.myapplication.api
enum class ApiStatus { LOADING, ERROR, DONE }

View File

@ -3,6 +3,7 @@ package com.example.myapplication.api
import com.example.myapplication.api.model.BasketServiceRemote
import com.example.myapplication.api.model.OrderRemote
import com.example.myapplication.api.model.OrderServiceRemote
import com.example.myapplication.api.model.ReportRemote
import com.example.myapplication.api.model.ServiceRemote
import com.example.myapplication.api.model.UserRemote
import com.example.myapplication.api.model.UserRemoteSignIn
@ -10,6 +11,8 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.DELETE
@ -20,7 +23,7 @@ import retrofit2.http.Path
import retrofit2.http.Query
interface ServerService {
//SNEAKER
// Service
@GET("service/get/{id}")
suspend fun getService(
@Path("id") id: Int,
@ -46,7 +49,7 @@ interface ServerService {
@DELETE("service/delete/{id}")
suspend fun deleteService(
@Path("id") id: Int
)
): Response<String>
//USER
@POST("user/signup")
@ -59,6 +62,11 @@ interface ServerService {
@Body user: UserRemoteSignIn
): UserRemote
@PUT("user/update")
suspend fun updateUser(
@Body user: UserRemote
): UserRemote
//BASKET
@POST("basket/addServiceToBasket")
suspend fun addServiceToBasket(
@ -140,6 +148,14 @@ interface ServerService {
suspend fun deleteOrder(
@Path("orderId") orderId: Int
)
//REPORT
@GET("report/getReport/{dateFrom}/{dateTo}")
suspend fun getReport(
@Path("dateFrom") dateFrom: Long,
@Path("dateTo") dateTo: Long
): ReportRemote
companion object {
private const val BASE_URL = "https://ftkfjb1l-8080.euw.devtunnels.ms/api/"
@ -148,7 +164,10 @@ interface ServerService {
fun getInstance(): ServerService {
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)

View File

@ -0,0 +1,24 @@
package com.example.myapplication.api.model
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Base64
import java.io.ByteArrayOutputStream
class RemoteConverters {
companion object {
private const val CHARSET_UTF8 = "UTF-8"
fun fromBitmap(bitmap: Bitmap): String {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 1, outputStream)
val byteArray = outputStream.toByteArray()
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
}
fun toBitmap(base64String: String): Bitmap {
val byteArray = Base64.decode(base64String, Base64.NO_WRAP)
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}
}

View File

@ -0,0 +1,11 @@
package com.example.myapplication.api.model
import kotlinx.serialization.Serializable
@Serializable
data class ReportRemote(
val countOrder: Int,
val totalEarn: Double? = 0.0,
val avgCheck: Double? = 0.0,
val serviceList: List<ServiceWithCount>
)

View File

@ -8,19 +8,19 @@ data class ServiceRemote (
val id: Int? = 0,
val name: String = "",
val price: Double = 0.0,
val photo: Int? = 0
val photo: String = ""
)
fun ServiceRemote.toService(): Service = Service(
id,
name,
price,
photo
RemoteConverters.toBitmap(photo)
)
fun Service.toServiceRemote():ServiceRemote = ServiceRemote(
serviceId,
name,
price,
photo
RemoteConverters.fromBitmap(photo)
)

View File

@ -0,0 +1,9 @@
package com.example.myapplication.api.model
import kotlinx.serialization.Serializable
@Serializable
data class ServiceWithCount(
val service: ServiceRemote,
val quantity: Int
)

View File

@ -12,7 +12,7 @@ data class UserRemote (
val email: String = "",
val password: String = "",
val role: RoleEnum = RoleEnum.User,
val photo: Int? = 0,
val photo: String = "",
val basketId: Int? = 0
)
@ -24,7 +24,7 @@ fun UserRemote.toUser(): User = User(
email,
password,
role,
photo,
RemoteConverters.toBitmap(photo),
basketId
)
@ -35,6 +35,6 @@ fun User.toUserRemote():UserRemote = UserRemote(
email,
password,
role,
photo,
RemoteConverters.fromBitmap(photo),
basketId
)

View File

@ -0,0 +1,11 @@
package com.example.myapplication.api.repository
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.model.ReportRemote
import com.example.myapplication.businessLogic.repository.ReportRepository
class RestReportRepository(private var service: ServerService) : ReportRepository {
override suspend fun getReportData(dateFrom: Long, dateTo: Long): ReportRemote {
return service.getReport(dateFrom, dateTo)
}
}

View File

@ -60,4 +60,9 @@ class RestServiceRepository(
dbServiceRepository.invalidateService(service.serviceId!!)
}catch (ex: Exception){}
}
override fun call(str: String): Flow<PagingData<Service>> {
return dbServiceRepository.call(str)
}
}

View File

@ -15,7 +15,7 @@ class RestUserRepository(
}
override suspend fun update(user: User) {
println()
service.updateUser(user.toUserRemote())
}
override suspend fun delete(user: User) {

View File

@ -0,0 +1,7 @@
package com.example.myapplication.businessLogic.repository
import com.example.myapplication.api.model.ReportRemote
interface ReportRepository {
suspend fun getReportData(dateFrom: Long, dateTo: Long): ReportRemote
}

View File

@ -10,4 +10,5 @@ interface ServiceRepository {
suspend fun delete(service: Service)
suspend fun getServiceById(id: Int): Service
suspend fun getAllServices(): Flow<PagingData<Service>>
fun call(str: String): Flow<PagingData<Service>>
}

View File

@ -12,13 +12,25 @@ object AppViewModelProvider {
ServiceViewModel(app().container.serviceRepo)
}
initializer {
UserViewModel(app().container.userRepo, app().container.basketRepo)
UserViewModel(
app().container.userRepo,
app().container.basketRepo
)
}
initializer {
OrderViewModel(app().container.orderRepo, app().container.basketRepo)
OrderViewModel(
app().container.orderRepo,
app().container.basketRepo
)
}
initializer {
BasketViewModel(app().container.basketRepo, app().container.orderRepo,)
BasketViewModel(
app().container.basketRepo,
app().container.orderRepo,
)
}
initializer {
ReportViewModel(app().container.reportRepo)
}
}
}

View File

@ -2,7 +2,6 @@ package com.example.myapplication.businessLogic.viewmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.GlobalUser
import com.example.myapplication.businessLogic.repository.BasketRepository
@ -17,9 +16,10 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
data class ServiceWithQuantity(val service: Service, val quantityStateFlow: MutableStateFlow<Int>)
class BasketViewModel(private val basketRepository: BasketRepository, private val orderRepository: OrderRepository) : ViewModel() {
class BasketViewModel(
private val basketRepository: BasketRepository,
private val orderRepository: OrderRepository
) : MyViewModel() {
private val _quantityMap = MutableStateFlow<Map<Int, MutableStateFlow<Int>>>(emptyMap())
val quantityMap: StateFlow<Map<Int, MutableStateFlow<Int>>> get() = _quantityMap
private val _total = mutableDoubleStateOf(0.00)
@ -28,6 +28,7 @@ class BasketViewModel(private val basketRepository: BasketRepository, private va
val myList: StateFlow<List<Service>> get() = _myList
private var _basketId = MutableStateFlow(0)
val basketId: StateFlow<Int> get() = _basketId
fun getQuantityState(basketId: Int, serviceId: Int): Flow<Int> {
val quantityMap = _quantityMap.value.toMutableMap()
val quantityStateFlow = quantityMap.getOrPut(serviceId) {
@ -59,14 +60,13 @@ class BasketViewModel(private val basketRepository: BasketRepository, private va
}
}
fun getBasketServices() {
viewModelScope.launch {
val basketId = GlobalUser.getInstance().getUser()?.basketId!!
basketRepository.getBasketWithServices(basketId)
.collect{services ->
_myList.value = services
}
}
fun getBasketServices() = viewModelScope.launch {
val userId = GlobalUser.getInstance().getUser()?.userId!!
basketRepository.getBasketWithServices(userId)
.collect{services ->
_myList.value = services
}
}
fun getUsersBasket(id: Int) {
@ -91,34 +91,42 @@ class BasketViewModel(private val basketRepository: BasketRepository, private va
}
fun incrementServiceQuantity(basketId: Int, serviceId: Int) {
viewModelScope.launch {
basketRepository.incrementServiceQuantity(basketId, serviceId)
val currentQuantity = getQuantityState(basketId, serviceId).first()
_quantityMap.value.toMutableMap().apply {
put(serviceId, MutableStateFlow(currentQuantity + 1))
runInScope(
actionSuccess = {
basketRepository.incrementServiceQuantity(basketId, serviceId)
}
updateSubTotal(GlobalUser.getInstance().getUser()?.userId!!)
)
var currentQuantity: Int = 0
viewModelScope.launch {
currentQuantity = getQuantityState(basketId, serviceId).first()
}
_quantityMap.value.toMutableMap().apply {
put(serviceId, MutableStateFlow(currentQuantity + 1))
}
updateSubTotal(GlobalUser.getInstance().getUser()?.userId!!)
}
fun decrementOrRemoveServiceQuantity(basketId: Int, serviceId: Int) {
viewModelScope.launch {
val currentQuantity = getQuantityState(basketId, serviceId).first()
if (currentQuantity > 1) {
basketRepository.decrementServiceQuantity(basketId, serviceId)
_quantityMap.value.toMutableMap().apply {
put(serviceId, MutableStateFlow(currentQuantity - 1))
}
fun decrementOrRemoveServiceQuantity(basketId: Int, serviceId: Int) = viewModelScope.launch{
val currentQuantity = getQuantityState(basketId, serviceId).first()
if (currentQuantity > 1) {
runInScope(
actionSuccess = { basketRepository.decrementServiceQuantity(basketId, serviceId) }
)
_quantityMap.value.toMutableMap().apply {
put(serviceId, MutableStateFlow(currentQuantity - 1))
}
else{
basketRepository.removeServiceFromBasket(basketId, serviceId)
_quantityMap.value.toMutableMap().apply {
remove(serviceId, MutableStateFlow(currentQuantity - 1))
}
getBasketServices()
}
updateSubTotal(GlobalUser.getInstance().getUser()?.userId!!)
}
else{
runInScope(
actionSuccess = { basketRepository.removeServiceFromBasket(basketId, serviceId) }
)
_quantityMap.value.toMutableMap().apply {
remove(serviceId, MutableStateFlow(currentQuantity - 1))
}
getBasketServices()
}
updateSubTotal(GlobalUser.getInstance().getUser()?.userId!!)
}
fun updateSubTotal(userId: Int) {

View File

@ -0,0 +1,50 @@
package com.example.myapplication.businessLogic.viewmodel
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.myapplication.api.ApiStatus
import kotlinx.coroutines.launch
import retrofit2.HttpException
import java.io.IOException
open class MyViewModel : ViewModel() {
var apiStatus by mutableStateOf(ApiStatus.DONE)
var apiError by mutableStateOf("")
private set
fun runInScope(
actionSuccess: suspend () -> Unit,
actionError: suspend () -> Unit
) {
viewModelScope.launch {
apiStatus = ApiStatus.LOADING
println(apiStatus)
runCatching {
actionSuccess()
apiStatus = ApiStatus.DONE
println(apiStatus)
apiError = ""
}.onFailure { e: Throwable ->
when (e) {
is IOException ,
is HttpException -> {
apiStatus = ApiStatus.ERROR
println(apiStatus)
actionError()
apiError = e.localizedMessage ?: e.toString()
}
else -> throw e
}
}
}
}
fun runInScope(actionSuccess: suspend () -> Unit) {
runInScope(actionSuccess, actionError = {})
}
}

View File

@ -3,7 +3,6 @@ package com.example.myapplication.businessLogic.viewmodel
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.GlobalUser
import com.example.myapplication.businessLogic.repository.BasketRepository
@ -13,10 +12,14 @@ import com.example.myapplication.model.Service
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import java.util.Date
class OrderViewModel(private val orderRepository: OrderRepository, private val basketRepository: BasketRepository) : ViewModel() {
class OrderViewModel(
private val orderRepository: OrderRepository,
private val basketRepository: BasketRepository
) : MyViewModel() {
private var _selectedItems = MutableLiveData<List<Service>>()
val selectedItems get() = _selectedItems
private val _total = mutableDoubleStateOf(0.00)
@ -31,22 +34,32 @@ class OrderViewModel(private val orderRepository: OrderRepository, private val b
total = getTotal(userId),
creatorUserId = userId
)
orderRepository.insert(order)
runInScope(
actionSuccess = { orderRepository.insert(order) }
)
}
return true
}
suspend fun getOrderWithServices(id: Int) : Flow<List<Service>> {
return orderRepository.getServiceFromOrder(id)
return try{
orderRepository.getServiceFromOrder(id)
}catch (e: Exception){
emptyFlow()
}
}
suspend fun getUserOrders(id: Int) {
viewModelScope.launch {
orderRepository.getUserOrders(id)
.collect{items ->
_orders.value = items
runInScope(
actionSuccess = {
viewModelScope.launch {
orderRepository.getUserOrders(id)
.collect{items ->
_orders.value = items
}
}
}
}
)
}
fun updateSelectedItems(items: List<Service>) {

View File

@ -0,0 +1,29 @@
package com.example.myapplication.businessLogic.viewmodel
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.example.myapplication.api.model.ServiceWithCount
import com.example.myapplication.businessLogic.repository.ReportRepository
import kotlinx.coroutines.launch
import java.util.Date
class ReportViewModel(private val reportRepository: ReportRepository): MyViewModel() {
val dateFrom = mutableStateOf(0L)
val dateTo = mutableStateOf(Date().time)
val avgSum = mutableStateOf(0.0)
val totalEarn = mutableStateOf(0.0)
val countOrder = mutableStateOf(0)
var serviceList = MutableLiveData<List<ServiceWithCount>>()
fun updateReportData(dateFrom: Long, dateTo: Long) {
viewModelScope.launch {
val report = reportRepository.getReportData(dateFrom, dateTo)
countOrder.value = report.countOrder
avgSum.value = report.avgCheck!!
totalEarn.value = report.totalEarn!!
serviceList.value = report.serviceList
}
}
}

View File

@ -1,48 +1,75 @@
package com.example.myapplication.businessLogic.viewmodel
import android.graphics.Bitmap
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import com.example.myapplication.R
import androidx.paging.cachedIn
import com.example.myapplication.businessLogic.repository.ServiceRepository
import com.example.myapplication.model.Service
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
class ServiceViewModel(private val serviceRepository: ServiceRepository): ViewModel() {
class ServiceViewModel(
private val serviceRepository: ServiceRepository
): MyViewModel() {
var name = mutableStateOf("")
var price = mutableDoubleStateOf(0.00)
var photo = mutableIntStateOf(R.drawable.image_service)
var service = mutableStateOf<Service>(Service(null,"", 0.0, null))
private val _serviceList = MutableStateFlow<PagingData<Service>>(PagingData.empty())
val serviceList: StateFlow<PagingData<Service>> get() = _serviceList
fun insertService() = viewModelScope.launch {
private val _serviceList = MutableStateFlow<Flow<PagingData<Service>>>(emptyFlow())
val serviceList: StateFlow<Flow<PagingData<Service>>> get() = _serviceList
init {
runInScope(
actionSuccess = { getServiceList() }
)
}
fun insertService(photo: Bitmap) {
val service = Service(
name = name.value,
price = price.doubleValue,
photo = photo.intValue
photo = photo
)
runInScope(
actionSuccess = { serviceRepository.insert(service) }
)
serviceRepository.insert(service)
}
fun deleteService(service : Service) = viewModelScope.launch {
serviceRepository.delete(service)
fun deleteService(service : Service) {
runInScope(
actionSuccess = {
serviceRepository.delete(service)
getServiceList()
}
)
}
fun updateService() = viewModelScope.launch {
serviceRepository.update(service.value)
fun updateService(service: Service) {
runInScope(
actionSuccess = {
serviceRepository.update(service)
getServiceList()
}
)
}
fun getServiceList(){
try{
viewModelScope.launch{
_serviceList.value = serviceRepository.getAllServices()
}
}catch(e: Exception){}
}
fun searchServicesByFilter(searchText: String){
viewModelScope.launch {
serviceRepository.getAllServices()
.collect{services ->
_serviceList.value = services
}
val filteredServices = serviceRepository.call(searchText).cachedIn(viewModelScope)
_serviceList.value = filteredServices
}
}
}

View File

@ -1,75 +1,55 @@
package com.example.myapplication.businessLogic.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import android.graphics.Bitmap
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.myapplication.GlobalUser
import com.example.myapplication.R
import com.example.myapplication.api.model.UserRemoteSignIn
import com.example.myapplication.businessLogic.repository.BasketRepository
import com.example.myapplication.businessLogic.repository.UserRepository
import com.example.myapplication.model.RoleEnum
import com.example.myapplication.model.User
import kotlinx.coroutines.launch
class UserViewModel(private val userRepository: UserRepository, private val basketRepository: BasketRepository): ViewModel() {
class UserViewModel(
private val userRepository: UserRepository,
private val basketRepository: BasketRepository
): MyViewModel() {
var name = mutableStateOf("")
var surname = mutableStateOf("")
var email = mutableStateOf("")
var password = mutableStateOf("")
var photo = mutableIntStateOf(R.drawable.icon_profile)
var isLoggedIn by mutableStateOf(false)
var isBusy by mutableStateOf(false)
// private val _loggedUser = MutableStateFlow(User(null,"null","null","null","null",RoleEnum.User))
// val loggedUser: StateFlow<User> = _loggedUser.asStateFlow()
fun createUser() = viewModelScope.launch {
val user = User(
name = name.value,
surname = surname.value,
email = email.value,
password = password.value,
role = RoleEnum.User,
photo = R.drawable.icon_profile
fun createUser(photo: Bitmap){
val user =
User(
name = name.value,
surname = surname.value,
email = email.value,
password = password.value,
role = RoleEnum.User,
photo = photo
)
runInScope(
actionSuccess = {
userRepository.insert(user)
}
)
// isBusy = true
userRepository.insert(user)
// isBusy = false
}
fun authUser() = viewModelScope.launch {
// isBusy = true
val user = userRepository.authUser(UserRemoteSignIn(email.value, password.value))
// isLoggedIn = true
// isBusy = false
GlobalUser.getInstance().setUser(user)
fun authUser() {
runInScope(
actionSuccess = {
val user = userRepository.authUser(UserRemoteSignIn(email.value, password.value))
GlobalUser.getInstance().setUser(user)
},
)
}
fun updateUser() = viewModelScope.launch {
val updateUser = GlobalUser.getInstance().getUser()
if(email.value != "")
updateUser?.email = email.value
else
updateUser?.email = updateUser?.email.toString()
if(name.value != "")
updateUser?.name = name.value
else
updateUser?.name = updateUser?.name.toString()
if(surname.value != "")
updateUser?.surname = surname.value
else
updateUser?.surname = updateUser?.surname.toString()
if(password.value != "")
updateUser?.password = password.value
else
updateUser?.password = updateUser?.password.toString()
//updateUser?.photo =
if (updateUser != null) {
userRepository.update(updateUser)
}
fun updateUser(user: User){
runInScope(
actionSuccess = {
userRepository.update(user)
}
)
}
fun isValidEmail(): Boolean {

View File

@ -1,5 +1,6 @@
package com.example.myapplication.composeui.Basket
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -24,16 +25,19 @@ 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.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.myapplication.GlobalUser
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.BasketViewModel
import com.example.myapplication.businessLogic.viewmodel.OrderViewModel
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.composeui.Profile.Login
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
@ -41,85 +45,100 @@ import com.example.myapplication.ui.theme.GreenBtn
@Composable
fun Basket(navController : NavHostController,
basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory),
orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)){
orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val user = GlobalUser.getInstance().getUser()
if (user == null){
Login(navController = navController)
}else{
basketViewModel.updateSubTotal(user.userId!!)
val total = basketViewModel.total.value
LaunchedEffect(basketViewModel){
basketViewModel.getBasketServices()
}
val serviceList by basketViewModel.myList.collectAsState()
orderViewModel.updateSelectedItems(serviceList)
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
for (item in serviceList){
BasketItemUI(item = item)
}
Box(modifier = Modifier
.padding(15.dp, 0.dp)
.clip(RoundedCornerShape(15.dp, 15.dp, 0.dp, 0.dp))
.background(Color.Transparent)
.height(130.dp),
){
Column (modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.background(Color.White)
.padding(PaddingValues(15.dp)),
){
Row (
horizontalArrangement = Arrangement.SpaceBetween,
){
val context = LocalContext.current
if (basketViewModel.apiStatus == ApiStatus.ERROR) {
Toast.makeText(context, "Error: " + basketViewModel.apiError, Toast.LENGTH_SHORT).show()
return
}
when (basketViewModel.apiStatus) {
ApiStatus.LOADING -> Loading()
ApiStatus.DONE ->
if (user == null) {
Login(navController = navController)
} else {
basketViewModel.updateSubTotal(user.userId!!)
val total = basketViewModel.total.value
LaunchedEffect(basketViewModel) {
basketViewModel.getBasketServices()
}
val serviceList by basketViewModel.myList.collectAsState()
orderViewModel.updateSelectedItems(serviceList)
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = "Total: ",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(1f)
)
Text(
text = "$$total",
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
}
Button(
onClick = {
orderViewModel.createOrder()
navController.navigate(NavItem.ListOfServices.route)
},
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.align(Alignment.BottomCenter),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Confirm order", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
for (item in serviceList) {
BasketItemUI(item = item)
}
Box(
modifier = Modifier
.padding(15.dp, 0.dp)
.clip(RoundedCornerShape(15.dp, 15.dp, 0.dp, 0.dp))
.background(Color.Transparent)
.height(130.dp),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.background(Color.White)
.padding(PaddingValues(15.dp)),
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "Total: ",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(1f)
)
Text(
text = "$$total",
style = MaterialTheme.typography.bodyMedium
)
}
}
Button(
onClick = {
orderViewModel.createOrder()
navController.navigate(NavItem.ListOfServices.route)
},
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.align(Alignment.BottomCenter),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Confirm order",
style = MaterialTheme.typography.bodyMedium.copy(Color.White)
)
}
}
}
}
}
else -> {}
}
}

View File

@ -29,9 +29,9 @@ 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.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
@ -69,9 +69,8 @@ fun BasketItemUI(item: Service, basketViewModel: BasketViewModel = viewModel(fac
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
item.photo?.let { painterResource(id = it) }?.let {
Image(
painter = it,
bitmap = item.photo.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
@ -79,7 +78,6 @@ fun BasketItemUI(item: Service, basketViewModel: BasketViewModel = viewModel(fac
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
}
Column(
modifier = Modifier

View File

@ -1,5 +1,14 @@
package com.example.myapplication.composeui.List_of_Services
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@ -11,7 +20,6 @@ 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.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
@ -21,206 +29,197 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterVertically
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.layout.ContentScale
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.ServiceViewModel
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextPrimary
import kotlinx.coroutines.Dispatchers
@Composable
fun AddService (navController: NavController, service: Service, serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val create = service.serviceId == null
LaunchedEffect(Dispatchers.Default){
if(!create){
serviceViewModel.service.value.serviceId = service.serviceId
serviceViewModel.service.value.photo = service.photo
fun AddService (
navController: NavController,
serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val context = LocalContext.current
val serviceImage = remember { mutableStateOf<Bitmap>(BitmapFactory.decodeResource(context.resources, R.drawable.image_service)) }
val imageData = remember { mutableStateOf<Uri?>(null) }
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
imageData.value = uri
}
imageData.value?.let {
if (Build.VERSION.SDK_INT < 28) {
serviceImage.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
serviceImage.value = ImageDecoder.decodeBitmap(source)
}
}
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp, 0.dp, 15.dp, 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
Box(
modifier = Modifier
.padding(0.dp, 0.dp, 0.dp, 10.dp)
.height(150.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(15.dp),
clip = false
),
){
Row(
if (serviceViewModel.apiStatus == ApiStatus.ERROR) {
Toast.makeText(context, "Error: " + serviceViewModel.apiError, Toast.LENGTH_SHORT).show()
return
}
when (serviceViewModel.apiStatus) {
ApiStatus.LOADING -> Loading()
ApiStatus.DONE ->
Column(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
){
if(create){
Image(
painter = painterResource(id = serviceViewModel.photo.intValue),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
.fillMaxSize()
.background(BlueMain)
.padding(15.dp, 0.dp, 15.dp, 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}else{
service.photo?.let { painterResource(id = it) }?.let {
}
Box(
modifier = Modifier
.padding(0.dp, 0.dp, 0.dp, 10.dp)
.height(150.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(15.dp),
clip = false
),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Image(
painter = it,
bitmap = serviceImage.value.asImageBitmap(),
contentDescription = null,
modifier = Modifier.align(CenterVertically),
)
Column(
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
verticalArrangement = Arrangement.Top,
) {
Text(
text = serviceViewModel.name.value,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium
)
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = serviceViewModel.price.doubleValue.toString(),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
Column(
) {
Row(modifier = Modifier.padding(vertical = 5.dp)) {
MyTextField(label = "Service name") {
serviceViewModel.name.value = it
}
}
Row(modifier = Modifier.padding(vertical = 5.dp)) {
MyTextField(label = "Price") {
try {
serviceViewModel.price.doubleValue = it.toDouble()
}catch (e: Exception){
serviceViewModel.price.doubleValue = 0.0
Toast.makeText(context, "Input correct price!", Toast.LENGTH_SHORT).show()
}
}
}
}
Button(
onClick = {
launcher.launch("image/*")
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Upload image",
style = MaterialTheme.typography.bodyMedium.copy(Color.White)
)
}
Button(
onClick = {
if (serviceViewModel.name.value == ""
|| serviceViewModel.price.doubleValue == 0.0)
Toast.makeText(context, "Input correct value!", Toast.LENGTH_SHORT).show()
else {
serviceViewModel.insertService(serviceImage.value)
navController.navigate(NavItem.AddService.route)
}
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Add service",
style = MaterialTheme.typography.bodyMedium.copy(Color.White)
)
}
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.Top,
){
if(create){
Text(
text = serviceViewModel.name.value,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
}else{
Text(
text = service.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
}
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
){
if(create){
Text(
text = serviceViewModel.price.doubleValue.toString(),
style = MaterialTheme.typography.bodyMedium,
)
}else{
Text(
text = service.price.toString(),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
if(create){
MyTextField(label = "Service name"){
serviceViewModel.name.value = it
}
}else{
MyTextField(label = service.name){
serviceViewModel.service.value.name = it
}
}
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
if(create){
MyTextField(label = "Price"){
serviceViewModel.price.doubleValue = it.toDouble()
}
}else{
MyTextField(label = service.price.toString()){
serviceViewModel.service.value.price = it.toDouble()
}
}
}
}
Button(
onClick = {
// val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
// launcher.launch(intent)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Upload image", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
Button(
onClick = {
if (create)
serviceViewModel.insertService()
else
serviceViewModel.service.let { serviceViewModel.updateService() }
navController.navigate(NavItem.ListOfServices.route)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
if(create) {
Text(text = "Add service", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}else{
Text(text = "Update service", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
}
else -> {}
}
}

View File

@ -0,0 +1,266 @@
package com.example.myapplication.composeui.List_of_Services
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
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.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
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.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.ServiceViewModel
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.BlueBorder
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextPrimary
@Composable
fun ChangeService(
service: Service,
navController: NavHostController,
serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val context = LocalContext.current
val name = remember { mutableStateOf(service.name) }
val price = remember { mutableStateOf(service.price) }
val photo = remember { mutableStateOf<Bitmap>(service.photo) }
val imageData = remember { mutableStateOf<Uri?>(null) }
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
imageData.value = uri
}
imageData.value?.let {
if (Build.VERSION.SDK_INT < 28) {
photo.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
photo.value = ImageDecoder.decodeBitmap(source)
}
}
when (serviceViewModel.apiStatus) {
ApiStatus.ERROR -> Toast.makeText(context, "Error: " + serviceViewModel.apiError, Toast.LENGTH_SHORT).show()
ApiStatus.LOADING -> Loading()
ApiStatus.DONE ->
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp, 0.dp, 15.dp, 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
Box(
modifier = Modifier
.padding(0.dp, 0.dp, 0.dp, 10.dp)
.height(150.dp)
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(15.dp),
clip = false
),
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Image(
bitmap = photo.value.asImageBitmap(),
contentDescription = null,
modifier = Modifier.align(Alignment.CenterVertically),
)
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.Top,
) {
Text(
text = service.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium
)
}
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = service.price.toString(),
style = MaterialTheme.typography.bodyMedium,
)
}
}
}
Column(
) {
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(15.dp, 5.dp),
){
BasicTextField(
value = name.value,
onValueChange = {
name.value = it
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
Spacer(modifier = Modifier.size(10.dp))
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(15.dp, 5.dp),
){
BasicTextField(
value = price.value.toString(),
onValueChange = {
try {
price.value = it.toDouble()
}catch (e: Exception){
price.value = 0.0
serviceViewModel.price.doubleValue = 0.0
Toast.makeText(context, "Input correct price!", Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
}
Button(
onClick = {
launcher.launch("image/*")
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Upload image",
style = MaterialTheme.typography.bodyMedium.copy(Color.White)
)
}
Button(
onClick = {
if (service.name == ""
|| service.price == 0.0
|| price.value == 0.0)
Toast.makeText(context, "Input correct value!", Toast.LENGTH_SHORT).show()
else{
serviceViewModel.updateService(
Service(
serviceId = service.serviceId,
name = name.value,
price = price.value,
photo = photo.value
)
)
}
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Save changes",
style = MaterialTheme.typography.bodyMedium.copy(Color.White)
)
}
}
else -> {}
}
}

View File

@ -1,6 +1,7 @@
package com.example.myapplication.composeui.List_of_Services
import SearchBar
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -13,8 +14,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
@ -22,8 +25,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.ServiceViewModel
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.BlueMain
@ -32,39 +37,53 @@ fun ListOfServices(navController: NavHostController, serviceViewModel: ServiceVi
LaunchedEffect(serviceViewModel){
serviceViewModel.getServiceList()
}
val services = serviceViewModel.serviceList.collectAsLazyPagingItems()
Column(modifier = Modifier.background(BlueMain).fillMaxSize().padding(bottom = 60.dp)){
Row(
val services = serviceViewModel.serviceList.collectAsState().value.collectAsLazyPagingItems()
val context = LocalContext.current
if (serviceViewModel.apiStatus == ApiStatus.ERROR){
Toast.makeText(context, "Error: " + serviceViewModel.apiError, Toast.LENGTH_SHORT).show()
return
}
when(serviceViewModel.apiStatus) {
ApiStatus.LOADING -> Loading()
ApiStatus.DONE -> Column(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
LazyColumn(
){
item{
SearchBar(
modifier = Modifier)
{
searchText ->
//TODO search logic
}
.background(BlueMain)
.fillMaxSize()
.padding(bottom = 60.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
items(
count = services.itemCount,
key = services.itemKey { service -> service.serviceId!! }
){
index: Int ->
val service: Service? = services[index]
if (service != null){
Service(navController, item = service)
LazyColumn(
) {
item {
SearchBar(
modifier = Modifier
)
{ searchText ->
serviceViewModel.searchServicesByFilter(searchText)
}
}
items(
count = services.itemCount,
key = services.itemKey { service -> service.serviceId!! }
) { index: Int ->
val service: Service? = services[index]
if (service != null) {
Service(navController, item = service)
}
}
}
}
else -> {}
}
}

View File

@ -33,9 +33,9 @@ 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.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
@ -53,7 +53,12 @@ import com.google.gson.Gson
import kotlinx.coroutines.launch
@Composable
fun Service(navController: NavHostController, item: Service, basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory), serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)){
fun Service(
navController: NavHostController,
item: Service,
basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory),
serviceViewModel: ServiceViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val user = GlobalUser.getInstance().getUser()
val basketId by basketViewModel.basketId.collectAsState()
LaunchedEffect(basketViewModel){
@ -78,9 +83,8 @@ fun Service(navController: NavHostController, item: Service, basketViewModel: Ba
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
){
item.photo?.let { painterResource(id = it) }?.let {
Image(
painter = it,
bitmap = item.photo.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
@ -88,7 +92,6 @@ fun Service(navController: NavHostController, item: Service, basketViewModel: Ba
.widthIn(max = (LocalConfiguration.current.screenWidthDp / 3).dp),
contentScale = ContentScale.FillHeight,
)
}
Column(
modifier = Modifier
@ -155,8 +158,7 @@ fun Service(navController: NavHostController, item: Service, basketViewModel: Ba
if(user?.role == RoleEnum.Admin){
Button(
onClick = {
serviceViewModel.service.value = item
navController.navigate("add_service/${Gson().toJson(item)}")
navController.navigate("change_service/${Gson().toJson(item)}")
},
modifier = Modifier
.size(42.dp)

View File

@ -1,49 +1,31 @@
package com.example.myapplication.composeui.Navbar
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
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.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ControlledComposition
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat.getColor
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.myapplication.R
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.BlueNavbar
import com.example.myapplication.ui.theme.GreenBtn
@ -76,12 +58,14 @@ fun NavBar(){
selected = isSelected,
icon = {
Icon(painterResource(screen.icon),
null,
modifier = Modifier,
GreenBtn)
null,
modifier = if (isSelected){
Modifier.background(BlueMain, CircleShape).size(50.dp).padding(8.dp)
} else {
Modifier.size(50.dp).padding(8.dp)
},
GreenBtn)
},
modifier = Modifier
.padding(15.dp),
onClick = {
navController.navigate(screen.route){
if (!isSelected) {

View File

@ -7,12 +7,14 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.example.myapplication.composeui.Basket.Basket
import com.example.myapplication.composeui.List_of_Services.AddService
import com.example.myapplication.composeui.List_of_Services.ChangeService
import com.example.myapplication.composeui.List_of_Services.ListOfServices
import com.example.myapplication.composeui.Orders.Orders
import com.example.myapplication.composeui.Profile.Login
import com.example.myapplication.composeui.Profile.Profile
import com.example.myapplication.composeui.Profile.ProfileChange
import com.example.myapplication.composeui.Profile.Registration
import com.example.myapplication.composeui.Profile.Report
import com.example.myapplication.model.Service
import com.google.gson.Gson
@ -59,12 +61,22 @@ fun NavController(navController : NavHostController){
}
composable(
NavItem.AddService.route
){
AddService(navController)
}
composable(
NavItem.ChangeService.route
){
backStackEntry ->
val serviceItemString = backStackEntry.arguments?.getString("serviceItem")
val serviceItem = Gson().fromJson(serviceItemString, Service::class.java)
serviceItem?.let { AddService(navController, it)
serviceItem?.let { ChangeService(it, navController)
}
}
composable(
NavItem.Report.route
){
Report()
}
}
}

View File

@ -36,7 +36,15 @@ sealed class NavItem(
R.drawable.icon_profile
)
object AddService : NavItem(
"add_service/{serviceItem}",
"add_service",
R.drawable.icon_list_of_services
)
object ChangeService : NavItem(
"change_service/{serviceItem}",
R.drawable.icon_list_of_services
)
object Report : NavItem(
"report",
R.drawable.profile
)
}

View File

@ -0,0 +1,46 @@
package com.example.myapplication.composeui.NetworkUI
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
@Composable
fun ErrorPlaceholder(message: String, onBack: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
fontSize = TextUnit(value = 20F, type = TextUnitType.Sp),
text = message,
color = Color(0xFFFF1744)
)
Spacer(modifier = Modifier.padding(bottom = 10.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { onBack() }
) {
Text(stringResource(id = R.string.back))
}
}
}

View File

@ -0,0 +1,41 @@
package com.example.myapplication.composeui.NetworkUI
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import com.example.myapplication.R
import com.example.myapplication.ui.theme.BlueMain
@Composable
fun Loading() {
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(10.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em)),
text = stringResource(id = R.string.loading)
)
}
}

View File

@ -1,31 +1,55 @@
package com.example.myapplication.composeui.Orders
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
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 com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.OrderViewModel
import com.example.myapplication.model.Order
import com.example.myapplication.model.Service
import kotlinx.coroutines.flow.first
import java.text.SimpleDateFormat
import java.util.Date
@Composable
fun OrderItem (order: Order){
fun OrderItem (
order: Order,
orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val dateFormat = SimpleDateFormat("dd-MM-yyyy")
var services by remember { mutableStateOf<List<Service>>(emptyList()) }
var expanded = remember { mutableStateOf(false) }
LaunchedEffect(order.orderId) {
services = orderViewModel.getOrderWithServices(order.orderId!!).first()
}
Column (
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
.clip(RoundedCornerShape(15.dp))
.clickable {
expanded.value = !expanded.value
}
){
Row (
horizontalArrangement = Arrangement.SpaceBetween,
@ -45,5 +69,12 @@ fun OrderItem (order: Order){
){
Text(text = dateFormat.format(Date(order.date)), style = MaterialTheme.typography.bodyMedium)
}
if(expanded.value){
LazyRow(){
itemsIndexed(services ?: emptyList()) { index, service ->
OrderScrollService(service)
}
}
}
}
}

View File

@ -22,7 +22,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun Orders (navController: NavController, orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)){
fun Orders (
navController: NavController,
orderViewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val user = GlobalUser.getInstance().getUser()
val ordersList by orderViewModel.orders.collectAsState()
LaunchedEffect(Unit) {

View File

@ -0,0 +1,77 @@
package com.example.myapplication.composeui.Orders
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import com.example.myapplication.model.Service
import com.example.myapplication.ui.theme.TextPrimary
@Composable
fun OrderScrollService(item: Service){
val maxWidth = (LocalConfiguration.current.screenWidthDp/2)
Box(
modifier = Modifier
.padding(4.dp)
.widthIn(max = maxWidth.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
){
Image(
bitmap = item.photo.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.padding(5.dp)
.widthIn(max = (maxWidth / 2).dp),
contentScale = ContentScale.FillHeight,
)
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (maxWidth / 2).dp),
verticalArrangement = Arrangement.Top,
){
Text(text = item.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
Text(text = item.price.toString(),
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
Text(
text = "$${item.price}",
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}

View File

@ -1,6 +1,5 @@
package com.example.myapplication.composeui.Profile
import android.view.Gravity
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -27,127 +26,146 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.GlobalUser
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.BasketViewModel
import com.example.myapplication.businessLogic.viewmodel.UserViewModel
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextSecondary
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable
fun Login (navController: NavController, userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory), basketViewModel: BasketViewModel = viewModel(factory = AppViewModelProvider.Factory)){
fun Login (
navController: NavController,
userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)
){
val context = LocalContext.current
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em))
)
}
Column (
){
var isEmailValid by remember { mutableStateOf(true)}
var isPasswordValid by remember { mutableStateOf(true)}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Password", onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
CoroutineScope(Dispatchers.Main).launch {
userViewModel.authUser()
navController.navigate(NavItem.Profile.route)
}
val toast = Toast.makeText(context, "message", Toast.LENGTH_SHORT)
toast.setGravity(Gravity.TOP, 0, 100)
toast.show()
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Login",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White)
)
}
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "Don't have an account? ",
style = MaterialTheme.typography.bodyMedium
.copy(TextSecondary)
)
Text(
text = "Sign up",
style = MaterialTheme.typography.bodyMedium
.copy(GreenBtn),
modifier = Modifier
.clickable { navController.navigate(NavItem.Registration.route) }
)
}
if(GlobalUser.getInstance().getUser() != null){
navController.navigate(NavItem.Profile.route)
}
when(userViewModel.apiStatus){
ApiStatus.LOADING -> Loading()
ApiStatus.ERROR -> {
Toast.makeText(context, "Error: " + userViewModel.apiError, Toast.LENGTH_SHORT).show()
userViewModel.apiStatus = ApiStatus.DONE
}
ApiStatus.DONE ->
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em))
)
}
Column (
){
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Password", visualTransformation = PasswordVisualTransformation(), onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
if (userViewModel.email.value == ""
|| !isEmailValid
|| !isPasswordValid){
Toast.makeText(context, "Input correct value!", Toast.LENGTH_SHORT).show()
}
else{
userViewModel.authUser()
}
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Login",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White)
)
}
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "Don't have an account? ",
style = MaterialTheme.typography.bodyMedium
.copy(TextSecondary)
)
Text(
text = "Sign up",
style = MaterialTheme.typography.bodyMedium
.copy(GreenBtn),
modifier = Modifier
.clickable { navController.navigate(NavItem.Registration.route) }
)
}
}
}
}

View File

@ -1,5 +1,6 @@
package com.example.myapplication.composeui.Profile
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -24,6 +25,7 @@ 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.graphics.asImageBitmap
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
@ -66,7 +68,11 @@ fun Profile(navController: NavHostController){
.size(200.dp)
.background(Color.White)
){
// TODO: upload profile image
Image(
bitmap = user.photo.asImageBitmap(),
contentDescription = null,
modifier = Modifier.align(Alignment.Center),
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
@ -129,7 +135,7 @@ fun Profile(navController: NavHostController){
}
if(user.role == RoleEnum.Admin){
Button(
onClick = { navController.navigate("add_service/{}") },
onClick = { navController.navigate(NavItem.AddService.route) },
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
@ -147,6 +153,25 @@ fun Profile(navController: NavHostController){
color = Color.White
)
}
Button(
onClick = { navController.navigate(NavItem.Report.route) },
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Reports",
style = MaterialTheme.typography.bodyMedium,
color = Color.White
)
}
}
Button(
onClick = {

View File

@ -1,20 +1,32 @@
package com.example.myapplication.composeui.Profile
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
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.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -26,7 +38,8 @@ 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.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
@ -34,126 +47,263 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.example.myapplication.GlobalUser
import com.example.myapplication.R
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.UserViewModel
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.model.User
import com.example.myapplication.ui.theme.BlueBorder
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
@Composable
fun ProfileChange (navController: NavHostController, userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val user = GlobalUser.getInstance().getUser()
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
val name = remember { mutableStateOf(user?.name) }
val surname = remember { mutableStateOf(user?.surname) }
val email = remember { mutableStateOf(user?.email) }
val password = remember { mutableStateOf(user?.password) }
val context = LocalContext.current
val imageData = remember { mutableStateOf<Uri?>(null) }
val profileImage = remember { mutableStateOf<Bitmap>(user?.photo!!) }
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {uri: Uri? ->
imageData.value = uri
}
Box(modifier = Modifier
.clip(CircleShape)
.size(200.dp)
.background(Color.White)
.padding(PaddingValues(0.dp))
){
Icon(
painterResource(id = R.drawable.upload),
contentDescription = null,
modifier = Modifier.align(Alignment.Center),
GreenBtn
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = "${user?.name} ${user?.surname}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = "${user?.email}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Name", onValueChanged = {userViewModel.name.value = it})
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Surname", onValueChanged = {userViewModel.surname.value = it})
}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Password", onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
userViewModel.updateUser()
navController.navigate(NavItem.Profile.route)
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Confirm changes", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
imageData.value?.let {
if (Build.VERSION.SDK_INT < 28) {
profileImage.value = MediaStore.Images
.Media.getBitmap(context.contentResolver, imageData.value)
} else {
val source = ImageDecoder
.createSource(context.contentResolver, imageData.value!!)
profileImage.value = ImageDecoder.decodeBitmap(source)
}
}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
when(userViewModel.apiStatus){
ApiStatus.LOADING -> Loading()
ApiStatus.DONE -> Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
Box(modifier = Modifier
.clip(CircleShape)
.size(200.dp)
.background(Color.White)
.padding(PaddingValues(0.dp))
){
Image(
bitmap = profileImage.value.asImageBitmap(),
contentDescription = null,
modifier = Modifier.align(Alignment.Center),
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = "${name.value} ${surname.value}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Box(modifier = Modifier.padding(15.dp)){
Text(
text = "${email.value}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
textAlign = TextAlign.Center,
)
}
Column (
){
Spacer(modifier = Modifier.size(5.dp))
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(15.dp, 5.dp),
){
BasicTextField(
value = name.value!!,
onValueChange = {
name.value = it
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
Spacer(modifier = Modifier.size(5.dp))
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(15.dp, 5.dp),
){
BasicTextField(
value = surname.value!!,
onValueChange = {
surname.value = it
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
Spacer(modifier = Modifier.size(5.dp))
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(15.dp, 5.dp),
){
BasicTextField(
value = email.value!!,
onValueChange = {
email.value = it
isEmailValid = isValidEmail(email.value!!)
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
Spacer(modifier = Modifier.size(5.dp))
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(15.dp))
.fillMaxWidth()
.background(Color.White)
.border(2.dp, color = BlueBorder, RoundedCornerShape(15.dp))
.height(45.dp)
.padding(15.dp, 5.dp),
){
BasicTextField(
value = password.value.toString(),
onValueChange = {
password.value = it
isPasswordValid = it.isNotEmpty()
},
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,
)
}
}
Spacer(modifier = Modifier.size(5.dp))
Button(
onClick = {
launcher.launch("image/*")
},
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Upload image",
style = MaterialTheme.typography.bodyMedium.copy(Color.White)
)
}
Spacer(modifier = Modifier.size(5.dp))
Button(
onClick = {
if (!isEmailValid
|| !isPasswordValid
|| name.value == ""
|| surname.value == "")
Toast.makeText(context, "Input correct value!", Toast.LENGTH_SHORT).show()
else {
val updatedUser = User(
user?.userId!!,
name = name.value!!,
surname = surname.value!!,
email = email.value!!,
password = password.value!!,
role = user.role,
photo = profileImage.value,
basketId = user.basketId)
userViewModel.updateUser(updatedUser)
GlobalUser.getInstance().setUser(updatedUser)
navController.navigate(NavItem.Profile.route)
}
},
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(text = "Confirm changes", style = MaterialTheme.typography.bodyMedium.copy(Color.White))
}
}
else -> {}
}
}
fun isValidEmail(str: String): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(str).matches()
}

View File

@ -1,5 +1,7 @@
package com.example.myapplication.composeui.Profile
import android.graphics.BitmapFactory
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -24,121 +26,147 @@ 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.platform.LocalContext
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.myapplication.R
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.UserViewModel
import com.example.myapplication.composeui.Navbar.NavItem
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.composeui.UIComponents.MyTextField
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextSecondary
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.UserViewModel
@Composable
fun Registration (navController: NavController, userViewModel: UserViewModel = viewModel(factory = AppViewModelProvider.Factory)){
Column(
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
){
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp),
horizontalArrangement = Arrangement.Center,
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em))
)
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Name", onValueChanged = { userViewModel.name.value = it })
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Surname", onValueChanged = { userViewModel.surname.value = it })
}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
val context = LocalContext.current
if (userViewModel.apiStatus == ApiStatus.ERROR){
Toast.makeText(context, "Error: " + userViewModel.apiError, Toast.LENGTH_SHORT).show()
return
}
var isEmailValid by remember { mutableStateOf(true) }
var isPasswordValid by remember { mutableStateOf(true) }
val photo = BitmapFactory.decodeResource(context.resources, R.drawable.profile)
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Password", onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
userViewModel.createUser()
navController.navigate(NavItem.Login.route)
},
when(userViewModel.apiStatus) {
ApiStatus.LOADING -> Loading()
ApiStatus.DONE ->
Column(
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Registration",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
){
Text(
text = "Already have a account? ",
style = MaterialTheme.typography.bodyMedium
.copy(TextSecondary)
)
Text(
text = "Login",
style = MaterialTheme.typography.bodyMedium
.copy(GreenBtn),
modifier = Modifier.clickable { navController.navigate(NavItem.Login.route) }
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 30.dp),
horizontalArrangement = Arrangement.Center,
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(16.0f, TextUnitType.Em))
)
}
Column (
){
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Name", onValueChanged = { userViewModel.name.value = it })
}
Row (modifier = Modifier.padding(vertical = 5.dp)){
MyTextField(label = "Surname", onValueChanged = { userViewModel.surname.value = it })
}
if (!isEmailValid) {
Text(
text = "Invalid email format",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Email", onValueChanged = {
userViewModel.email.value = it
isEmailValid = userViewModel.isValidEmail()
})
}
if (!isPasswordValid) {
Text(
text = "Password is required",
style = MaterialTheme.typography.bodyMedium
.copy(Color.Red, fontSize = TextUnit(4.0f, TextUnitType.Em))
)
}
Row (modifier = Modifier
.padding(vertical = 5.dp)
){
MyTextField(label = "Password", visualTransformation = PasswordVisualTransformation(), onValueChanged = {
userViewModel.password.value = it
isPasswordValid = it.isNotEmpty()
})
}
}
Button(
onClick = {
if (userViewModel.email.value == ""
|| userViewModel.name.value == ""
|| userViewModel.surname.value == ""
|| !isEmailValid
|| !isPasswordValid)
Toast.makeText(context, "Input correct value!", Toast.LENGTH_SHORT).show()
else {
userViewModel.createUser(photo)
navController.navigate(NavItem.Login.route)
}
},
modifier = Modifier
.height(60.dp)
.padding(top = 10.dp)
.fillMaxWidth()
.clip(CircleShape),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
contentPadding = PaddingValues(0.dp),
) {
Text(
text = "Registration",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White)
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "Already have a account? ",
style = MaterialTheme.typography.bodyMedium
.copy(TextSecondary)
)
Text(
text = "Login",
style = MaterialTheme.typography.bodyMedium
.copy(GreenBtn),
modifier = Modifier.clickable { navController.navigate(NavItem.Login.route) }
)
}
}
else -> {}
}
}

View File

@ -0,0 +1,98 @@
package com.example.myapplication.composeui.Profile
import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.ReportViewModel
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.TextPrimary
@Composable
fun Report(reportViewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)){
val context = LocalContext.current
if (reportViewModel.apiStatus == ApiStatus.ERROR){
Toast.makeText(context, "Error: " + reportViewModel.apiError, Toast.LENGTH_SHORT).show()
return
}
if(reportViewModel.countOrder.value == 0){
ReportDatePick()
}
else{
when(reportViewModel.apiStatus){
ApiStatus.LOADING -> Loading()
ApiStatus.DONE ->
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Column(modifier = Modifier
.background(Color.White, RoundedCornerShape(15.dp)).padding(10.dp)
){
Text(
text = "Number of orders: ${reportViewModel.countOrder.value}",
style = MaterialTheme.typography.bodyMedium.copy(
TextPrimary, fontSize = TextUnit(4.0f, TextUnitType.Em)),
color = TextPrimary
)
Text(
text = "Total earn: ${reportViewModel.totalEarn.value}",
style = MaterialTheme.typography.bodyMedium.copy(
TextPrimary, fontSize = TextUnit(4.0f, TextUnitType.Em)),
color = TextPrimary
)
Text(
text = "Average check cost: ${reportViewModel.avgSum.value}",
style = MaterialTheme.typography.bodyMedium.copy(
TextPrimary, fontSize = TextUnit(4.0f, TextUnitType.Em)),
color = TextPrimary
)
}
Spacer(modifier = Modifier.size(15.dp))
Box(modifier = Modifier
.background(Color.White, RoundedCornerShape(15.dp))
.padding(10.dp)
){
Text(
text = "Top 10 popular services:",
style = MaterialTheme.typography.bodyMedium.copy(
TextPrimary, fontSize = TextUnit(4.0f, TextUnitType.Em)),
color = TextPrimary
)
}
LazyRow {
itemsIndexed(reportViewModel.serviceList.value ?: emptyList()) { index, service ->
ServiceReportCard(service)
}
}
}
else -> {}
}
}
}

View File

@ -0,0 +1,102 @@
package com.example.myapplication.composeui.Profile
import android.widget.Toast
import androidx.compose.foundation.background
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
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.platform.LocalContext
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.myapplication.api.ApiStatus
import com.example.myapplication.businessLogic.viewmodel.AppViewModelProvider
import com.example.myapplication.businessLogic.viewmodel.ReportViewModel
import com.example.myapplication.composeui.NetworkUI.Loading
import com.example.myapplication.composeui.UIComponents.DatePicker
import com.example.myapplication.ui.theme.BlueMain
import com.example.myapplication.ui.theme.RedBtn
@Composable
fun ReportDatePick(reportViewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
val context = LocalContext.current
if (reportViewModel.apiStatus == ApiStatus.ERROR){
Toast.makeText(context, "Error: " + reportViewModel.apiError, Toast.LENGTH_SHORT).show()
return
}
when(reportViewModel.apiStatus){
ApiStatus.LOADING -> Loading()
ApiStatus.DONE ->
Column (
modifier = Modifier
.fillMaxSize()
.background(BlueMain)
.padding(15.dp)
.padding(bottom = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally,
){
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
){
Text(
text = "PetMed",
style = MaterialTheme.typography.bodyMedium
.copy(Color.White, fontSize = TextUnit(8.0f, TextUnitType.Em))
)
}
DatePicker(
selectedDate = reportViewModel.dateFrom,
onDateSelected = { date ->
reportViewModel.dateFrom.value = date
}
)
Spacer(modifier = Modifier.height(16.dp))
DatePicker(
selectedDate = reportViewModel.dateTo,
onDateSelected = { date ->
reportViewModel.dateTo.value = date
},
)
Button(
onClick = {
if(reportViewModel.dateFrom.value <= reportViewModel.dateTo.value){
reportViewModel.updateReportData(reportViewModel.dateFrom.value, reportViewModel.dateTo.value)
}else{
Toast.makeText(context, "Incorrect date!", Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = RedBtn,
contentColor = Color.White
),
) {
Text("Get reports")
}
}
else -> {}
}
}

View File

@ -0,0 +1,78 @@
package com.example.myapplication.composeui.Profile
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import com.example.myapplication.api.model.RemoteConverters
import com.example.myapplication.api.model.ServiceWithCount
import com.example.myapplication.ui.theme.TextPrimary
@Composable
fun ServiceReportCard(item: ServiceWithCount){
val maxWidth = (LocalConfiguration.current.screenWidthDp/2)
Box(
modifier = Modifier
.padding(4.dp)
.widthIn(max = maxWidth.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(145.dp)
.background(color = Color.White, RoundedCornerShape(15.dp))
.padding(15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
){
Image(
bitmap = RemoteConverters.toBitmap(item.service.photo).asImageBitmap(),
contentDescription = null,
modifier = Modifier
.fillMaxHeight()
.heightIn(min = 100.dp)
.padding(5.dp)
.widthIn(max = (maxWidth / 2).dp),
contentScale = ContentScale.FillHeight,
)
Column(
modifier = Modifier
.fillMaxHeight()
.widthIn(max = (maxWidth / 2).dp),
verticalArrangement = Arrangement.Top,
){
Text(text = item.service.name,
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
Text(text = item.service.price.toString(),
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
Text(
text = "x${item.quantity}",
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}

View File

@ -0,0 +1,87 @@
package com.example.myapplication.composeui.UIComponents
import android.app.DatePickerDialog
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
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.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.example.myapplication.ui.theme.GreenBtn
import com.example.myapplication.ui.theme.TextPrimary
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
@Composable
fun DatePicker(
selectedDate: MutableState<Long>,
onDateSelected: (Long) -> Unit,
value: Long? = null
) {
val context = LocalContext.current
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
val date = Date()
val datePickerDialog = remember { mutableStateOf(DatePickerDialog(context)) }
val dateFormatter = remember { SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()) }
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Selected Date: ${dateFormatter.format(selectedDate.value)}",
color = TextPrimary,
style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.size(16.dp))
Button(
modifier = Modifier
.height(60.dp)
.fillMaxWidth()
.clip(CircleShape)
.padding(vertical = 5.dp),
colors = ButtonDefaults.buttonColors(
containerColor = GreenBtn,
contentColor = Color.White
),
onClick = {
datePickerDialog.value = DatePickerDialog(
context,
{ _, year: Int, month: Int, dayOfMonth: Int ->
val selectedDateInMillis = Calendar.getInstance().apply {
set(year, month, dayOfMonth)
}.timeInMillis
selectedDate.value = selectedDateInMillis
onDateSelected(selectedDateInMillis)
},
year,
month,
day
)
datePickerDialog.value.show()
}
) {
Text(text = "Open Date Picker")
}
}
}

View File

@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import com.example.myapplication.ui.theme.BlueBorder
import com.example.myapplication.ui.theme.TextSecondary
@ -28,6 +29,7 @@ import com.example.myapplication.ui.theme.TextSecondary
@Composable
fun MyTextField (
label: String,
visualTransformation: VisualTransformation = VisualTransformation.None,
onValueChanged: (String) -> Unit,
){
val textState = remember { mutableStateOf(TextFieldValue()) }
@ -48,8 +50,6 @@ fun MyTextField (
text = label,
style = MaterialTheme.typography.bodyMedium.copy(color = TextSecondary),
)
}else{
}
BasicTextField(
value = text,
@ -57,6 +57,7 @@ fun MyTextField (
textState.value = it
onValueChanged(it.text)
},
visualTransformation = visualTransformation,
modifier = Modifier.fillMaxWidth(),
textStyle = MaterialTheme.typography.bodyMedium,
singleLine = true,

View File

@ -4,6 +4,7 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import com.example.myapplication.database.dao.BasketDao
import com.example.myapplication.database.dao.OrderDao
@ -12,6 +13,7 @@ import com.example.myapplication.database.dao.ServiceDao
import com.example.myapplication.database.dao.UserDao
import com.example.myapplication.model.Basket
import com.example.myapplication.model.BasketService
import com.example.myapplication.model.Converters
import com.example.myapplication.model.Order
import com.example.myapplication.model.OrderService
import com.example.myapplication.model.RemoteKeys
@ -21,7 +23,14 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Database(entities = [User::class, Service::class, Order::class, OrderService::class, Basket::class, BasketService::class, RemoteKeys::class], version = 10)
@Database(entities = [User::class,
Service::class,
Order::class,
OrderService::class,
Basket::class,
BasketService::class,
RemoteKeys::class], version = 14)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase(){
abstract fun serviceDao(): ServiceDao
abstract fun userDao(): UserDao
@ -36,15 +45,7 @@ abstract class AppDatabase : RoomDatabase(){
private var INSTANCE: AppDatabase? = null
suspend fun populateDatabase() {
// INSTANCE?.let { database ->
// // User
// val userDao = database.userDao()
// val user1 = User(null, "Danil", "Markov", "danil@mail.ru", "123", RoleEnum.Admin)
// userDao.insert(user1)
// val basketDao = database.basketDao()
// val basket1 = Basket(null, user1.userId!!)
// basketDao.insert(basket1)
// }
}
fun getInstance(appContext: Context): AppDatabase {

View File

@ -26,4 +26,6 @@ interface ServiceDao {
suspend fun deleteAll()
@Query("DELETE FROM tbl_service WHERE serviceId = :id")
suspend fun invalidateService(id: Int)
@Query("SELECT * FROM tbl_service WHERE LOWER(name) LIKE '%' || LOWER(:searchString) || '%' ")
fun findServices(searchString: String): PagingSource<Int, Service>
}

View File

@ -28,4 +28,14 @@ class ServiceRepositoryImpl(private val serviceDao: ServiceDao): ServiceReposito
serviceDao.insert(*services.toTypedArray())
fun getAllServicesPagingSource(): PagingSource<Int, Service> = serviceDao.getAll()
suspend fun invalidateService(id: Int) = serviceDao.invalidateService(id)
override fun call(str: String): Flow<PagingData<Service>> {
return Pager(
PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
) {
serviceDao.findServices(str)
}.flow
}
}

View File

@ -2,6 +2,7 @@ package com.example.myapplication.di
import com.example.myapplication.businessLogic.repository.BasketRepository
import com.example.myapplication.businessLogic.repository.OrderRepository
import com.example.myapplication.businessLogic.repository.ReportRepository
import com.example.myapplication.businessLogic.repository.ServiceRepository
import com.example.myapplication.businessLogic.repository.UserRepository
@ -10,6 +11,7 @@ interface AppContainer {
val userRepo: UserRepository
val orderRepo: OrderRepository
val basketRepo: BasketRepository
val reportRepo: ReportRepository
companion object {
const val TIMEOUT = 5000L

View File

@ -4,10 +4,12 @@ import android.content.Context
import com.example.myapplication.api.ServerService
import com.example.myapplication.api.repository.RestBasketRepository
import com.example.myapplication.api.repository.RestOrderRepository
import com.example.myapplication.api.repository.RestReportRepository
import com.example.myapplication.api.repository.RestServiceRepository
import com.example.myapplication.api.repository.RestUserRepository
import com.example.myapplication.businessLogic.repository.BasketRepository
import com.example.myapplication.businessLogic.repository.OrderRepository
import com.example.myapplication.businessLogic.repository.ReportRepository
import com.example.myapplication.businessLogic.repository.ServiceRepository
import com.example.myapplication.businessLogic.repository.UserRepository
import com.example.myapplication.database.AppDatabase
@ -32,6 +34,9 @@ class AppDataContainer(context: Context) : AppContainer {
override val basketRepo: BasketRepository by lazy{
RestBasketRepository(ServerService.getInstance())
}
override val reportRepo: ReportRepository by lazy{
RestReportRepository(ServerService.getInstance())
}
private val serviceRepository: ServiceRepositoryImpl by lazy {
ServiceRepositoryImpl(AppDatabase.getInstance(context).serviceDao())

View File

@ -0,0 +1,20 @@
package com.example.myapplication.model
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.room.TypeConverter
import java.io.ByteArrayOutputStream
class Converters {
@TypeConverter
fun fromBitmap(bitmap: Bitmap) : ByteArray {
val outputStream = ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 1, outputStream)
return outputStream.toByteArray()
}
@TypeConverter
fun toBitmap(byteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
}

View File

@ -1,5 +1,6 @@
package com.example.myapplication.model
import android.graphics.Bitmap
import androidx.room.Entity
import androidx.room.PrimaryKey
@ -9,5 +10,5 @@ data class Service (
var serviceId: Int? = null,
var name: String,
var price: Double,
var photo: Int? = null
var photo: Bitmap
)

View File

@ -1,5 +1,6 @@
package com.example.myapplication.model
import android.graphics.Bitmap
import androidx.room.Entity
import androidx.room.PrimaryKey
@ -12,6 +13,6 @@ data class User(
var email: String,
var password: String,
val role: RoleEnum,
val photo: Int? = null,
var photo: Bitmap,
val basketId: Int? = null
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -2,4 +2,6 @@
<string name="app_name">PetMed</string>
<string name="not_auth_error">Please login</string>
<string name="placeholder_search">Search…</string>
<string name="loading">Loading...</string>
<string name="back">Return</string>
</resources>