diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 863e329..78d0bc2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -108,8 +108,6 @@ dependencies { implementation(libs.compose) - implementation(libs.kefirbb) - implementation(libs.acra.http) implementation(libs.androidx.room.runtime) diff --git a/app/release/baselineProfiles/0/ru.sweetbread.unn-v1(1.0)-release.dm b/app/release/baselineProfiles/0/ru.sweetbread.unn-v1(1.0)-release.dm new file mode 100644 index 0000000..bd7e58f Binary files /dev/null and b/app/release/baselineProfiles/0/ru.sweetbread.unn-v1(1.0)-release.dm differ diff --git a/app/release/baselineProfiles/1/ru.sweetbread.unn-v1(1.0)-release.dm b/app/release/baselineProfiles/1/ru.sweetbread.unn-v1(1.0)-release.dm new file mode 100644 index 0000000..47e0fc4 Binary files /dev/null and b/app/release/baselineProfiles/1/ru.sweetbread.unn-v1(1.0)-release.dm differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..f0d43a2 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "ru.sweetbread.unn", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "ru.sweetbread.unn-v1(1.0)-release.apk" + } + ], + "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/ru.sweetbread.unn-v1(1.0)-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/ru.sweetbread.unn-v1(1.0)-release.dm" + ] + } + ], + "minSdkVersionForDexing": 26 +} \ No newline at end of file diff --git a/app/release/ru.sweetbread.unn-v1(1.0)-release.apk b/app/release/ru.sweetbread.unn-v1(1.0)-release.apk new file mode 100644 index 0000000..a6ab8b9 Binary files /dev/null and b/app/release/ru.sweetbread.unn-v1(1.0)-release.apk differ diff --git a/app/src/main/java/ru/sweetbread/unn/API.kt b/app/src/main/java/ru/sweetbread/unn/API.kt index 7144d8b..973ed44 100644 --- a/app/src/main/java/ru/sweetbread/unn/API.kt +++ b/app/src/main/java/ru/sweetbread/unn/API.kt @@ -19,11 +19,14 @@ import ru.sweetbread.unn.db.loadSchedule import ru.sweetbread.unn.db.loadUserByBitrixId import ru.sweetbread.unn.ui.layout.LoginData import ru.sweetbread.unn.ui.layout.client +import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime +import java.time.ZoneId import java.time.format.DateTimeFormatter + private lateinit var PHPSESSID: String private lateinit var CSRF: String lateinit var ME: User @@ -31,8 +34,10 @@ lateinit var ME: User const val portalURL = "https://portal.unn.ru" const val ruzapiURL = "$portalURL/ruzapi" const val vuzapiURL = "$portalURL/bitrix/vuz/api" +const val prtl2URL = "$portalURL/portal2/api" const val restURL = "$portalURL/rest" + enum class Type(val s: String) { Student("student"), Group("group"), @@ -109,7 +114,6 @@ class User( class Post( val id: Int, - val blogId: Int, val authorId: Int, val enableComments: Boolean, val numComments: Int, @@ -123,6 +127,7 @@ class AvatarSet( val small: String ) + /** * Authorize user by [login] and [password] * @@ -149,10 +154,8 @@ suspend fun auth( if (r.status.value == 302) { PHPSESSID = """PHPSESSID=([\w\d]+)""".toRegex().find(r.headers["Set-Cookie"]!!)!!.groupValues[1] - GlobalScope.launch(Dispatchers.IO) { getMyself(login) getCSRF() - } return true } return false @@ -162,42 +165,44 @@ suspend fun auth( * Save info about current [User] in memory */ private suspend fun getMyself(login: String) { - GlobalScope.launch(Dispatchers.IO) { - val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo") { - parameter("uns", login.substring(1)) - }.bodyAsText()) + // WARNING: trailing / is important, 'cuz API devs are eating shit + val studentinfo = JSONObject(client.get("$ruzapiURL/studentinfo/") { + header("Cookie", "PHPSESSID=$PHPSESSID") + parameter("uns", login.drop(1)) + }.bodyAsText()) - val user = JSONObject( - client.get("$vuzapiURL/user") { - header("Cookie", "PHPSESSID=$PHPSESSID") - }.bodyAsText() - ) + val user = JSONObject( + client.get("$vuzapiURL/user") { + header("Cookie", "PHPSESSID=$PHPSESSID") + }.bodyAsText() + ) - ME = User( - unnId = studentinfo.getString("id").toInt(), - bitrixId = user.getInt("bitrix_id"), - userId = user.getInt("id"), - type = when (studentinfo.getString("type")) { - "lecturer" -> Type.Lecturer // ig,,, - else -> Type.Student - }, - email = user.getString("email"), - nameRu = user.getString("fullname"), - nameEn = user.getString("fullname_en"), - isMale = user.getString("sex") == "M", - birthday = LocalDate.parse( - user.getString("birthdate"), - DateTimeFormatter.ofPattern("yyyy-MM-dd") - ), - avatar = user.getJSONObject("photo").let { - AvatarSet( - it.getString("orig"), - it.getString("thumbnail"), - it.getString("small"), - ) - } - ) - } + Log.d("studentInfo", studentinfo.toString(2)) + + ME = User( + unnId = studentinfo.getString("id").toInt(), + bitrixId = user.getInt("bitrix_id"), + userId = user.getInt("id"), + type = when (studentinfo.getString("type")) { + "lecturer" -> Type.Lecturer // ig,,, + else -> Type.Student + }, + email = user.getString("email"), + nameRu = user.getString("fullname"), + nameEn = user.getString("fullname_en"), + isMale = user.getString("sex") == "M", + birthday = Instant + .parse(user.getString("birthdate")) + .atZone(ZoneId.of("Europe/Moscow")) + .toLocalDate(), + avatar = user.getJSONObject("photo").let { + AvatarSet( + it.getString("orig"), + it.getString("thumbnail"), + it.getString("small"), + ) + } + ) } suspend fun getScheduleDay( @@ -223,7 +228,7 @@ suspend fun getSchedule( start: LocalDate, finish: LocalDate ): ArrayList { - val unnDatePattern = DateTimeFormatter.ofPattern("yyyy.MM.dd") + val unnDatePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd") val r = client.get("$ruzapiURL/schedule/${type.s}/$id") { parameter("start", start.format(unnDatePattern)) @@ -310,28 +315,26 @@ suspend fun getCSRF() { } suspend fun getBlogposts(): ArrayList { - val r = client.get("$restURL/log.blogpost.get") { + val r = client.get("$prtl2URL/news.php") { header("Cookie", "PHPSESSID=$PHPSESSID") - parameter("sessid", CSRF) + header("x-bitrix-sessid-token", CSRF) } - val json = JSONObject(r.bodyAsText()) - val result = json.getJSONArray("result") + val result = JSONArray(r.bodyAsText()) val out = arrayListOf() for (i in 0 until result.length()) { val el = result.getJSONObject(i) out.add( Post( - id = el.getString("ID").toInt(), - blogId = el.getString("BLOG_ID").toInt(), - authorId = el.getString("AUTHOR_ID").toInt(), - enableComments = el.getString("ENABLE_COMMENTS") == "Y", - numComments = el.getString("NUM_COMMENTS").toInt(), + id = el.getString("id").toInt(), + authorId = el.getJSONObject("author").getInt("id").toInt(), + enableComments = true, // FIXME: Delete the field or get correct value + numComments = el.getString("commentsnum").toInt(), date = LocalDateTime.parse( - el.getString("DATE_PUBLISH"), - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'+03:00'") + el.getString("time"), + DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") ), - content = el.getString("DETAIL_TEXT") + content = el.getString("fulltext") ) ) } @@ -375,10 +378,10 @@ suspend fun getUser(id: Int): User { nameRu = json.getString("fullname"), nameEn = json.getString("fullname_en"), isMale = json.getString("sex") == "M", - birthday = LocalDate.parse( - json.getString("birthdate"), - DateTimeFormatter.ofPattern("yyyy-MM-dd") - ), + birthday = Instant + .parse(json.getString("birthdate")) + .atZone(ZoneId.of("Europe/Moscow")) + .toLocalDate(), avatar = json.getJSONObject("photo").let { AvatarSet( it.getString("orig"), diff --git a/app/src/main/java/ru/sweetbread/unn/UNNApp.kt b/app/src/main/java/ru/sweetbread/unn/UNNApp.kt index b5b20e6..4ce7f50 100644 --- a/app/src/main/java/ru/sweetbread/unn/UNNApp.kt +++ b/app/src/main/java/ru/sweetbread/unn/UNNApp.kt @@ -11,15 +11,15 @@ class UNNApp : Application() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) - initAcra { - buildConfigClass = BuildConfig::class.java - reportFormat = StringFormat.JSON - httpSender { - uri = BuildConfig.ACRA_URL - basicAuthLogin = BuildConfig.ACRA_LOGIN - basicAuthPassword = BuildConfig.ACRA_PASS - httpMethod = HttpSender.Method.POST - } - } +// initAcra { +// buildConfigClass = BuildConfig::class.java +// reportFormat = StringFormat.JSON +// httpSender { +// uri = BuildConfig.ACRA_URL +// basicAuthLogin = BuildConfig.ACRA_LOGIN +// basicAuthPassword = BuildConfig.ACRA_PASS +// httpMethod = HttpSender.Method.POST +// } +// } } } \ No newline at end of file diff --git a/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt b/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt index e24fc19..f70bea2 100644 --- a/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt +++ b/app/src/main/java/ru/sweetbread/unn/ui/composes/Blogpost.kt @@ -1,3 +1,8 @@ +/* + * Created by sweetbread + * Copyright (c) 2025. All rights reserved. + */ + package ru.sweetbread.unn.ui.composes import android.text.util.Linkify @@ -46,8 +51,6 @@ import com.google.android.material.textview.MaterialTextView import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import org.kefirsf.bb.BBProcessorFactory -import org.kefirsf.bb.TextProcessor import ru.sweetbread.unn.AvatarSet import ru.sweetbread.unn.Post import ru.sweetbread.unn.R @@ -169,12 +172,11 @@ fun UserItem(modifier: Modifier = Modifier, user: User, info: String? = null) { @NonRestartableComposable fun PostItem(modifier: Modifier = Modifier, post: Post, extended: Boolean = false) { var user: User? by remember { mutableStateOf(null) } - val processor = remember { BBProcessorFactory.getInstance().create() } var html: String by remember { mutableStateOf("") } LaunchedEffect(post) { - html = toHtml(processor, post) + html = post.content user = getUserByBitrixId(post.authorId) } @@ -218,45 +220,6 @@ fun PostItem(modifier: Modifier = Modifier, post: Post, extended: Boolean = fals } } -private fun toHtml( - processor: TextProcessor, - post: Post -): String { - Log.d("toHTML | original", post.content) - - val result = - post.content.replace("""\[URL=(.+?)](.+?)\[/URL]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[2]?.value}" - }.replace("""\[FONT=(.+?)](.*?)\[/FONT]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[2]?.value}" - }.replace("""\[SIZE=(.+?)](.*?)\[/SIZE]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[2]?.value}" - }.replace( - """\[CENTER]\[JUSTIFY]\[CENTER](.*?)\[/CENTER]\[/JUSTIFY]\[/CENTER]""".toRegex( - RegexOption.DOT_MATCHES_ALL - ) - ) { - "${it.groups[1]?.value}
" - }.replace("""\[CENTER](.*?)\[/CENTER]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[1]?.value}
" - }.replace("""\[JUSTIFY](.*?)\[/JUSTIFY]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[1]?.value}
" - }.replace("""\[B](.*?)\[/B]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[1]?.value}" - }.replace("""\[U](.*?)\[/U]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "${it.groups[1]?.value}" - }.replace("""\[P](.*?)\[/P]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - "

${it.groups[1]?.value}

" - /*}.replace("""\[DISK FILE ID=n(\d+)]""".toRegex(RegexOption.DOT_MATCHES_ALL)) { - ""*/ - }.replace("""\[IMG .+].+?\[/IMG]""".toRegex(RegexOption.DOT_MATCHES_ALL), "") - /*.replace("\n", "\n
")*/ - - Log.d("toHTML | result", result) - - return result -} - @Preview @Composable @@ -278,7 +241,6 @@ fun UserItemPreview() { fun PostItemPreview() { val post = Post( id = 154923, - blogId = 121212, authorId = 165945, enableComments = true, numComments = 0, diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/unn_logo.png b/app/src/main/res/drawable/unn_logo.png new file mode 100644 index 0000000..d75612c Binary files /dev/null and b/app/src/main/res/drawable/unn_logo.png differ diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 0000000..8b570f1 --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2a43bda..694d809 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] acraHttp = "5.11.3" -agp = "8.5.2" +agp = "8.7.0" calendar = "2.5.4" coilCompose = "2.7.0" compose = "1.6.4" # Updating this will cause an error! @@ -23,8 +23,8 @@ activity = "1.9.2" navigationCompose = "2.7.7" # Updating this will cause an error! roomRuntime = "2.6.1" secretsGradlePlugin = "2.0.1" -splittiesFunPackAndroidBaseWithViewsDsl = "3.0.0" -kefirbb = "1.5" +splitties = "3.0.0" +materialIconsCoreAndroid = "1.7.8" [libraries] acra-http = { module = "ch.acra:acra-http", version.ref = "acraHttp" } @@ -62,8 +62,9 @@ androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecy androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } -splitties-funpack-android-base-with-views-dsl = { module = "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl", version.ref = "splittiesFunPackAndroidBaseWithViewsDsl" } -kefirbb = { group = "org.kefirsf", name = "kefirbb", version.ref = "kefirbb" } +splitties-base = { module = "com.louiscad.splitties:splitties-fun-pack-android-base-with-views-dsl", version.ref = "splitties" } +splitties-room = { module = "com.louiscad.splitties:splitties-arch-room", version.ref = "splitties" } +androidx-material-icons-core-android = { group = "androidx.compose.material", name = "material-icons-core-android", version.ref = "materialIconsCoreAndroid" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c66b25b..c75acdb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sat Mar 16 18:30:45 MSK 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/secrets.properties b/secrets.properties new file mode 100644 index 0000000..ada183d --- /dev/null +++ b/secrets.properties @@ -0,0 +1,3 @@ +ACRA_URL=https://bugs.coders-squad.com/report +ACRA_LOGIN=pMJkqPlNLX4kQ3kK +ACRA_PASS=HYY72oV4ybmpCggC \ No newline at end of file