[UI] Add list of contributors in Settings. (#15)

* Contributors item in settings

* Move contributors activity to settings package && actualize branch

* Update AndroidManifest.xml

* Getting contributors from github api

* Cleaning code

* Fetching data from szkolny api, displaying content, a lot of changes :D

* Strings

* Remove androidx legacy library

* Revert manifest changes

* Remove logging in SzkolnyApi

* Fix app name spelling

* Revert changes to dimens.xml

* Refactor contributors code

* Revert changes to dimens.xml

Again

* Revert changes to build.gradle

* Revert changes to gradle-wrapper.properties

* Revert changes to gradle.properties

* Make user name nullable

* Add caching, refactor plurals, add progress bar

* Update contributors UI

* Shorten activity name in manifest

* Remove unneeded line break

* Remove fragment_translators.xml

Co-authored-by: Kuba Szczodrzyński <kuba@szczodrzynski.pl>
This commit is contained in:
Tomasz F 2021-09-08 19:11:14 +02:00 committed by GitHub
parent 7b4effe889
commit 452271e8c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 432 additions and 6 deletions

View File

@ -1,6 +1,7 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-parcelize'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'

View File

@ -146,6 +146,7 @@
android:configChanges="orientation|keyboardHidden"
android:theme="@style/Base.Theme.AppCompat" />
<activity android:name=".ui.modules.base.BuildInvalidActivity" />
<activity android:name=".ui.modules.settings.contributors.ContributorsActivity" />
<!-- _____ _
| __ \ (_)

View File

@ -14,6 +14,7 @@ import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.text.*
import android.text.style.CharacterStyle
import android.text.style.ForegroundColorSpan
@ -737,6 +738,7 @@ fun Bundle(vararg properties: Pair<String, Any?>): Bundle {
is Short -> putShort(property.first, property.second as Short)
is Double -> putDouble(property.first, property.second as Double)
is Boolean -> putBoolean(property.first, property.second as Boolean)
is Array<*> -> putParcelableArray(property.first, property.second as Array<out Parcelable>)
}
}
}

View File

@ -22,10 +22,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.ApiCacheIntercept
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.SignatureInterceptor
import pl.szczodrzynski.edziennik.data.api.szkolny.interceptor.Signing
import pl.szczodrzynski.edziennik.data.api.szkolny.request.*
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.RegisterAvailabilityStatus
import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update
import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse
import pl.szczodrzynski.edziennik.data.api.szkolny.response.*
import pl.szczodrzynski.edziennik.data.db.entity.Event
import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage
import pl.szczodrzynski.edziennik.data.db.entity.Notification
@ -373,6 +370,15 @@ class SzkolnyApi(val app: App) : CoroutineScope {
throw SzkolnyApiException(null)
}
@Throws(Exception::class)
fun getContributors(): ContributorsResponse {
val response = api.contributors().execute()
if (response.isSuccessful && response.body() != null) {
return parseResponse(response)
}
throw SzkolnyApiException(null)
}
@Throws(Exception::class)
fun getFirebaseToken(registerName: String): String {
val response = api.firebaseToken(registerName).execute()

View File

@ -27,6 +27,9 @@ interface SzkolnyService {
@POST("appUser")
fun appUser(@Body request: AppUserRequest): Call<ApiResponse<Unit>>
@GET("contributors/android")
fun contributors(): Call<ApiResponse<ContributorsResponse>>
@GET("updates/app")
fun updates(@Query("channel") channel: String = "release"): Call<ApiResponse<List<Update>>>

View File

@ -0,0 +1,20 @@
package pl.szczodrzynski.edziennik.data.api.szkolny.response
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
data class ContributorsResponse(
val contributors: List<Item>,
val translators: List<Item>
) {
@Parcelize
data class Item(
val login: String,
val name: String?,
val avatarUrl: String,
val profileUrl: String,
val itemUrl: String,
val contributions: Int?
) : Parcelable
}

View File

@ -9,7 +9,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
class FragmentLazyPagerAdapter(
fragmentManager: FragmentManager,
swipeRefreshLayout: SwipeRefreshLayout,
swipeRefreshLayout: SwipeRefreshLayout? = null,
val fragments: List<Pair<LazyFragment, CharSequence>>
) : LazyPagerAdapter(fragmentManager, swipeRefreshLayout) {
override fun getPage(position: Int) = fragments[position].first

View File

@ -24,6 +24,7 @@ import pl.szczodrzynski.edziennik.ui.dialogs.changelog.ChangelogDialog
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsCard
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsLicenseActivity
import pl.szczodrzynski.edziennik.ui.modules.settings.SettingsUtil
import pl.szczodrzynski.edziennik.ui.modules.settings.contributors.ContributorsActivity
import pl.szczodrzynski.edziennik.utils.Utils
import kotlin.coroutines.CoroutineContext
@ -90,6 +91,14 @@ class SettingsAboutCard(util: SettingsUtil) : SettingsCard(util), CoroutineScope
it.subText = BuildConfig.VERSION_NAME + ", " + BuildConfig.BUILD_TYPE
},
util.createActionItem(
text = R.string.settings_about_contributors_text,
subText = R.string.settings_about_contributors_subtext,
icon = CommunityMaterial.Icon.cmd_account_group_outline
) {
activity.startActivity(Intent(activity, ContributorsActivity::class.java))
},
util.createMoreItem(card, items = listOf(
util.createActionItem(
text = R.string.settings_about_changelog_text,

View File

@ -0,0 +1,83 @@
package pl.szczodrzynski.edziennik.ui.modules.settings.contributors
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.Bundle
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ContributorsResponse
import pl.szczodrzynski.edziennik.databinding.ContributorsActivityBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.FragmentLazyPagerAdapter
import pl.szczodrzynski.edziennik.ui.modules.error.ErrorSnackbar
import kotlin.coroutines.CoroutineContext
class ContributorsActivity : AppCompatActivity(), CoroutineScope {
companion object {
private const val TAG = "ContributorsActivity"
private var contributors: ContributorsResponse? = null
}
private lateinit var app: App
private lateinit var b: ContributorsActivityBinding
private var job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
// local/private variables go here
private val errorSnackbar: ErrorSnackbar by lazy { ErrorSnackbar(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
app = application as App
b = ContributorsActivityBinding.inflate(layoutInflater)
setContentView(b.root)
b.progressBar.isVisible = true
b.tabLayout.isVisible = false
b.viewPager.isVisible = false
launch {
contributors = contributors ?: SzkolnyApi(app).runCatching(errorSnackbar) {
getContributors()
} ?: return@launch
val pagerAdapter = FragmentLazyPagerAdapter(
supportFragmentManager,
fragments = listOf(
ContributorsFragment().apply {
arguments = Bundle(
"items" to contributors!!.contributors.toTypedArray(),
"quantityPluralRes" to R.plurals.contributions_quantity,
)
} to getString(R.string.contributors),
ContributorsFragment().apply {
arguments = Bundle(
"items" to contributors!!.translators.toTypedArray(),
"quantityPluralRes" to R.plurals.translations_quantity,
)
} to getString(R.string.translators),
)
)
b.viewPager.apply {
offscreenPageLimit = 1
adapter = pagerAdapter
b.tabLayout.setupWithViewPager(this)
}
b.progressBar.isVisible = false
b.tabLayout.isVisible = true
b.viewPager.isVisible = true
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) Kuba Szczodrzyński 2021-9-7.
*/
package pl.szczodrzynski.edziennik.ui.modules.settings.contributors
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.PluralsRes
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import pl.szczodrzynski.edziennik.R
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ContributorsResponse
import pl.szczodrzynski.edziennik.databinding.ContributorsListItemBinding
import pl.szczodrzynski.edziennik.plural
import pl.szczodrzynski.edziennik.setText
import pl.szczodrzynski.edziennik.utils.Utils
class ContributorsAdapter(
val activity: AppCompatActivity,
val items: List<ContributorsResponse.Item>,
@PluralsRes
val quantityPluralRes: Int
) : RecyclerView.Adapter<ContributorsAdapter.ViewHolder>() {
companion object {
private const val TAG = "ContributorsAdapter"
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = ContributorsListItemBinding.inflate(inflater, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
val b = holder.b
b.text.text = item.name ?: item.login
b.subtext.setText(
R.string.contributors_subtext_format,
item.login,
activity.plural(
quantityPluralRes,
item.contributions ?: 0
)
)
b.image.load(item.avatarUrl) {
transformations(CircleCropTransformation())
}
b.root.setOnClickListener {
Utils.openUrl(activity, item.itemUrl)
}
}
override fun getItemCount() = items.size
class ViewHolder(val b: ContributorsListItemBinding) : RecyclerView.ViewHolder(b.root)
}

View File

@ -0,0 +1,47 @@
package pl.szczodrzynski.edziennik.ui.modules.settings.contributors
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import pl.szczodrzynski.edziennik.App
import pl.szczodrzynski.edziennik.data.api.szkolny.response.ContributorsResponse
import pl.szczodrzynski.edziennik.databinding.ContributorsListFragmentBinding
import pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyFragment
import pl.szczodrzynski.edziennik.utils.SimpleDividerItemDecoration
class ContributorsFragment : LazyFragment() {
companion object {
private const val TAG = "ContributorsFragment"
}
private lateinit var app: App
private lateinit var activity: ContributorsActivity
private lateinit var b: ContributorsListFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
activity = (getActivity() as ContributorsActivity?) ?: return null
context ?: return null
app = activity.application as App
b = ContributorsListFragmentBinding.inflate(inflater)
return b.root
}
override fun onPageCreated(): Boolean {
val contributorsArray = requireArguments().getParcelableArray("items") as Array<ContributorsResponse.Item>
val contributors = contributorsArray.toList()
val quantityPluralRes = requireArguments().getInt("quantityPluralRes")
val adapter = ContributorsAdapter(activity, contributors, quantityPluralRes)
b.list.adapter = adapter
b.list.apply {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerItemDecoration(context))
addOnScrollListener(onScrollListener)
}
return true
}
}

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
tools:visibility="gone" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?actionBarSize">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="?actionBarSize"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="?actionBarSize"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.7">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:scaleType="center"
android:scaleX="0.8"
android:scaleY="0.8"
android:src="@mipmap/ic_splash" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="64dp"
android:fontFamily="sans-serif-light"
android:gravity="center"
android:text="@string/app_name"
android:textAppearance="@style/NavView.TextView.Large"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="64dp"
android:layout_marginBottom="48dp"
android:fontFamily="sans-serif-light"
android:gravity="center"
android:text="@string/contributors_headline"
android:textAppearance="@style/NavView.TextView.Medium" />
</LinearLayout>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:layout_gravity="bottom"
android:background="?android:colorBackground"
android:foreground="@color/colorSurface_2dp"
android:minHeight="?actionBarSize"
android:visibility="gone"
app:tabIndicatorColor="?colorPrimary"
app:tabMode="auto"
app:tabSelectedTextColor="?colorPrimary"
app:tabTextColor="?android:textColorPrimary"
tools:visibility="visible" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<pl.szczodrzynski.edziennik.ui.modules.base.lazypager.LazyViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</layout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/contributors_list_item" />
</layout>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="72dp"
android:layout_height="72dp"
android:padding="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_account_circle" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical"
android:paddingHorizontal="8dp"
android:paddingVertical="16dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/NavView.TextView.Subtitle"
tools:text="der Librüsch" />
<TextView
android:id="@+id/subtext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="@style/NavView.TextView.Helper"
tools:text="[at]librüsch - ∞ contributions" />
</LinearLayout>
</LinearLayout>
</layout>

View File

@ -1234,4 +1234,6 @@
<string name="you_are_offline_title">Netzwerkverbindung</string>
<string name="login_summary_account_child">(Kind)</string>
<string name="login_summary_account_parent">(Elternteil)</string>
<string name="settings_about_contributors_text">Anwendungsentwickler</string>
<string name="settings_about_contributors_subtext">Liste der Szkolny-Entwickler</string>
</resources>

View File

@ -55,4 +55,13 @@
<item quantity="one">%1$s - %2$d unread</item>
<item quantity="other">%1$s - %2$d unread</item>
</plurals>
</resources>
<plurals name="contributions_quantity">
<item quantity="one">%d contribution</item>
<item quantity="other">%d contributions</item>
</plurals>
<plurals name="translations_quantity">
<item quantity="one">%d translation</item>
<item quantity="other">%d translations</item>
</plurals>
</resources>

View File

@ -1237,6 +1237,10 @@
<string name="permissions_attachment">In order to download the file, you have to grant file storage permission for the application.\n\nClick OK to grant the permission.</string>
<string name="permissions_denied">You denied the required permissions for the application.\n\nIn order to grant the permission, open the Permissions screen for Szkolny.eu in phone settings.\n\nClick OK to open app settings now.</string>
<string name="permissions_required">Required permissions</string>
<string name="settings_about_contributors_text">App contributors</string>
<string name="settings_about_contributors_subtext">List of Szkolny.eu contributors</string>
<string name="contributors">Contributors</string>
<string name="translators">Translators</string>
<string name="settings_register_hide_sticks_from_old">Hide sticks from old</string>
<string name="build_official">Official build</string>
<string name="build_platform_play">Google Play</string>

View File

@ -159,4 +159,15 @@
<item quantity="few">%d oceny</item> <!-- 2, 3, 4, 32, 33, 34 -->
<item quantity="other">%d ocen</item> <!-- 5, 10, 12, 13, 21, 25 -->
</plurals>
<plurals name="contributions_quantity">
<item quantity="one">%d commit</item>
<item quantity="few">%d commity</item>
<item quantity="other">%d commit\'ów</item>
</plurals>
<plurals name="translations_quantity">
<item quantity="one">%d tłumaczenie</item>
<item quantity="few">%d tłumaczenia</item>
<item quantity="other">%d tłumaczeń</item>
</plurals>
</resources>

View File

@ -1391,6 +1391,10 @@
<string name="see_also">Zobacz także</string>
<string name="settings_about_homepage_text">Wejdź na stronę aplikacji</string>
<string name="settings_about_homepage_subtext">Uzyskaj pomoc lub wesprzyj autorów</string>
<string name="settings_about_contributors_text">Twórcy aplikacji</string>
<string name="settings_about_contributors_subtext">Lista twórców Szkolnego</string>
<string name="contributors">Współtwórcy</string>
<string name="translators">Tłumacze</string>
<string name="settings_about_github_text">Kod źródłowy</string>
<string name="settings_about_github_subtext">Pomóż w rozwoju aplikacji na GitHubie</string>
<string name="profile_config_name_hint">Nazwa profilu</string>
@ -1457,4 +1461,6 @@
<string name="notification_grade_long_format">Ocena: %s (waga %s)\nPrzedmiot: %s\nKategoria: %s\nOpis: %s\nNauczyciel: %s</string>
<string name="notification_notice_long_format">Rodzaj: %s\nNauczyciel: %s\nTreść: %s</string>
<string name="notification_attendance_long_format">Rodzaj: %s\nTermin: %s, %s\nNr lekcji: %s\nPrzedmiot: %s\nNauczyciel: %s\nTemat lekcji: %s</string>
<string name="contributors_subtext_format" translatable="false">\@%s - %s</string>
<string name="contributors_headline">Najłatwiejszy sposób na korzystanie z e-dziennika.</string>
</resources>