Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
davidtakac committed Dec 18, 2020
2 parents 9d38a1b + 9af72d1 commit 4e8d342
Show file tree
Hide file tree
Showing 49 changed files with 777 additions and 764 deletions.
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Ferit Raspored - Studentska aplikacija
# Ferit Schedule - Student app

A wrapper app for [Ferit's](https://www.ferit.unios.hr) online class schedule.
[Latest version - on Play Store](https://play.google.com/store/apps/details?id=os.dtakac.feritraspored)
Expand All @@ -7,13 +7,36 @@ A wrapper app for [Ferit's](https://www.ferit.unios.hr) online class schedule.
If you have suggestions or wish to maintain the project after I finish college, please let me know at
developer.takac@gmail.com.

## Features
- Automatically load current week and scroll to current day
- Skip days after a specific time
## App features
- Heavily de-bloated original web-page (remove redundant elements and all scripts)
- Automatically load current week and scroll to current day for your study programme and year
- Switch between Croatian and English schedule language in-app
- Display class period on its block
- Skip days after specified time
- Skip saturdays
- Highlight blocks based on filter
- Dark and light theme

## Code features
- Model-View-ViewModel
- View binding
- Single-activity, multi-fragment
- ViewModel and LiveData for configuration changes (rotation and theme)
- Navigation components
- Koin dependency injection
- Kotlin coroutines for network calls
- Kotlin property delegates
- Android preferences library
- Native day and night mode
- Material design
- Core library de-sugaring for that sweet Java 8 time

## Credit
To my friends:

- [Robert Sorić](https://rsoric.github.io/) for the initial highlight code
- [Luka Šimić](https://github.com/lsimic) for helping me with CSS
- [Luka Šimić](https://github.com/lsimic) for helping me with CSS and general web problems
- [Tomislav Rekić](https://github.com/tomislavrekic) for alpha-testing and design help
- Antonio Firić for alpha-testing

Who I thank for their support and feedback.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ android {
applicationId "os.dtakac.feritraspored"
minSdkVersion 21
targetSdkVersion 30
versionCode 18
versionName "2.3.0"
versionCode 19
versionName "2.3.1"
}
buildTypes {
release {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/assets/dark_theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ body {
background-color: #212121 !important;
color: #BDBDBD !important;
}
.vrijeme-mobitel .satnica {
.vrijeme-mobitel .satnica, .vrijeme .satnica {
color: #BDBDBD !important;
}
.blokovi:not(.Ne) p {
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/java/os/dtakac/feritraspored/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import org.koin.core.context.startKoin
import os.dtakac.feritraspored.common.di.appModule
import os.dtakac.feritraspored.common.preferences.PreferenceRepository
import os.dtakac.feritraspored.schedule.di.scheduleModule
import os.dtakac.feritraspored.settings.di.settingsModule

class App: Application() {
@Suppress("unused")
class App : Application() {
private val prefs: PreferenceRepository by inject()

override fun onCreate() {
Expand All @@ -21,10 +21,10 @@ class App: Application() {
}

private fun initKoin() {
startKoin{
startKoin {
androidLogger()
androidContext(this@App)
modules(appModule, settingsModule, scheduleModule)
modules(appModule, scheduleModule)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package os.dtakac.feritraspored.common.assets

interface AssetProvider {
suspend fun readFile(fileName: String): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package os.dtakac.feritraspored.common.assets

import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.*

class AssetProviderImpl(
private val applicationContext: Context
) : AssetProvider {
override suspend fun readFile(fileName: String): String {
return withContext(Dispatchers.IO) {
val stringBuilder = StringBuilder()

@Suppress("BlockingMethodInNonBlockingContext")
applicationContext.assets.open(fileName).use {
val scanner = Scanner(it)
while (scanner.hasNextLine()) {
stringBuilder.append(scanner.nextLine())
}
stringBuilder.toString()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
package os.dtakac.feritraspored.common.constants

const val DIALOG_WHATS_NEW = "whats_new"
const val DIALOG_TIME_PICKER = "time_picker"
const val DIALOG_FILTERS_HELP = "filters_help"
const val DIALOG_COURSE_IDENTIFIER_HELP = "course_identifier_help"

/**
* Default click debounce interval in ms.
*/
const val DEBOUNCE_INTERVAL = 300L
import androidx.appcompat.app.AppCompatDelegate
import os.dtakac.feritraspored.R

const val DEBOUNCE_INTERVAL_MS = 300L
const val SHOW_CHANGELOG = false

object SharedPreferenceKeys {
const val SKIP_SAT = "skip_saturday_key"
const val SKIP_DAY = "skip_day_key"
const val FILTERS = "group_highlight_key"
const val PROGRAMME = "programme_key"
const val YEAR = "year_key"
const val TIME_PICKER = "timepicker_key"
const val TIME_HOUR = "hour_key"
const val TIME_MINUTE = "minute_key"
const val SETTINGS_MODIFIED = "settings_modified"
const val LOAD_ON_RESUME = "load_on_resume_key"
const val FILTERS_TOGGLE = "group_toggle_key"
const val FILTERS_HELP = "group_help_key"
const val THEME = "prefkey_theme"
const val CHANGELOG = "changelog_key"
const val DEV_MESSAGE = "dev_msg_key"
const val VERSION = "version_key"
const val IDENTIFIER = "course_identifier"
const val TIME_ON_BLOCKS = "key_time_on_blocks"
const val IDENTIFIER_HELP = "course_identifier_help"
const val SCHEDULE_LANG = "url_key"
}

object DialogKeys {
const val WHATS_NEW = "whats_new"
const val TIME_PICKER = "time_picker"
const val FILTERS_HELP = "filters_help"
const val COURSE_IDENTIFIER_HELP = "course_identifier_help"
}

val SCHEDULE_LANGUAGES = arrayOf(
"https://www.ferit.unios.hr/studenti/raspored-nastave-i-ispita/%s/%s",
"https://www.ferit.unios.hr/students/schedule-of-classes-and-exams/%s/%s"
)

val SUPPORT_EMAILS = arrayOf(
"developer.takac@gmail.com"
)

val THEME_NAMES_TO_VALUES = linkedMapOf(
R.string.theme_option_system to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
R.string.theme_option_light to AppCompatDelegate.MODE_NIGHT_NO,
R.string.theme_option_dark to AppCompatDelegate.MODE_NIGHT_YES
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package os.dtakac.feritraspored.common.data

import android.content.res.Resources
import androidx.annotation.StringRes

data class StringResourceWithArgs(
@StringRes val content: Int,
val args: List<String>? = null
) {
fun buildString(resources: Resources): String {
val string = resources.getString(content)
return if (!args.isNullOrEmpty()) {
string.format(*args.toTypedArray())
} else {
string
}
}
}
15 changes: 9 additions & 6 deletions app/src/main/java/os/dtakac/feritraspored/common/di/AppModule.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package os.dtakac.feritraspored.common.di

import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import os.dtakac.feritraspored.common.assets.AssetProvider
import os.dtakac.feritraspored.common.assets.AssetProviderImpl
import os.dtakac.feritraspored.common.network.NetworkChecker
import os.dtakac.feritraspored.common.network.NetworkCheckerImpl
import os.dtakac.feritraspored.common.preferences.PreferenceRepository
import os.dtakac.feritraspored.common.preferences.PreferenceRepositoryImpl
import os.dtakac.feritraspored.common.resources.ResourceRepository
import os.dtakac.feritraspored.common.resources.ResourceRepositoryImpl

val appModule = module {
single<ResourceRepository>{ ResourceRepositoryImpl(androidContext()) }
single<PreferenceRepository> {
PreferenceRepositoryImpl(get(), PreferenceManager.getDefaultSharedPreferences(get()))
}
single<AssetProvider> { AssetProviderImpl(androidContext()) }
single<NetworkChecker> { NetworkCheckerImpl(androidContext()) }
single<SharedPreferences> { PreferenceManager.getDefaultSharedPreferences(get()) }
single<PreferenceRepository> { PreferenceRepositoryImpl(get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package os.dtakac.feritraspored.common.extensions
import android.content.res.Configuration

fun Configuration.isNightMode(): Boolean {
return when(uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
return when (uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) {
Configuration.UI_MODE_NIGHT_YES -> true
Configuration.UI_MODE_NIGHT_NO -> false
else -> false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import android.net.Uri
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import os.dtakac.feritraspored.R
import os.dtakac.feritraspored.common.data.EmailEditorData
import os.dtakac.feritraspored.common.constants.SUPPORT_EMAILS

fun Context.openEmailEditor(data: EmailEditorData) {
fun Context.openEmailEditor(
subject: String = "",
content: String = ""
) {
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:")
intent.putExtra(Intent.EXTRA_EMAIL, resources.getStringArray(R.array.email_addresses))
intent.putExtra(Intent.EXTRA_SUBJECT, data.subject)
intent.putExtra(Intent.EXTRA_TEXT, data.content)
intent.putExtra(Intent.EXTRA_EMAIL, SUPPORT_EMAILS)
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
intent.putExtra(Intent.EXTRA_TEXT, content)
startActivity(Intent.createChooser(intent, resources.getString(R.string.label_email_via)))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package os.dtakac.feritraspored.common.extensions

import android.util.DisplayMetrics
import android.util.TypedValue

fun DisplayMetrics.toPixels(dp: Float): Float =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, this)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package os.dtakac.feritraspored.common.extensions
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentManager
import os.dtakac.feritraspored.R
import os.dtakac.feritraspored.common.constants.DIALOG_WHATS_NEW
import os.dtakac.feritraspored.common.constants.DialogKeys
import os.dtakac.feritraspored.common.view.dialog_info.InfoDialogFragment

fun FragmentManager.showInfoDialog(
Expand All @@ -21,6 +21,6 @@ fun FragmentManager.showChangelog() {
titleResId = R.string.title_whats_new,
contentResId = R.string.content_whats_new,
dismissResId = R.string.dismiss_whats_new,
key = DIALOG_WHATS_NEW
key = DialogKeys.WHATS_NEW
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package os.dtakac.feritraspored.common.extensions

import java.lang.Exception
import java.time.LocalDate
import java.time.format.DateTimeFormatter

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package os.dtakac.feritraspored.common.extensions

import java.time.LocalTime
import java.time.format.DateTimeFormatter

private val TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm")

fun LocalTime.timeFormat(): String = try {
format(TIME_FORMAT)
} catch (e: Exception) {
""
}

fun String.toLocalTime(): LocalTime = try {
LocalTime.parse(this, TIME_FORMAT)
} catch (e: Exception) {
LocalTime.of(0, 0)
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package os.dtakac.feritraspored.common.extensions

import androidx.annotation.StringRes
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import java.lang.IllegalStateException
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class PreferenceManagerDelegate<T: Preference>(
@StringRes private val keyResId: Int
): ReadOnlyProperty<PreferenceFragmentCompat, T> {
class PreferenceManagerDelegate<T : Preference>(
private val key: String
) : ReadOnlyProperty<PreferenceFragmentCompat, T> {
private var preference: T? = null

override operator fun getValue(thisRef: PreferenceFragmentCompat, property: KProperty<*>): T {
Expand All @@ -20,10 +18,9 @@ class PreferenceManagerDelegate<T: Preference>(
}

private fun findPreference(preferenceFragment: PreferenceFragmentCompat): T {
val key = preferenceFragment.getString(keyResId)
return preferenceFragment.findPreference(key)
?: throw IllegalStateException("Could not find preference for key $key.")
}
}

fun <T: Preference> preference(@StringRes keyResId: Int) = PreferenceManagerDelegate<T>(keyResId)
fun <T : Preference> preference(key: String) = PreferenceManagerDelegate<T>(key)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package os.dtakac.feritraspored.common.network

interface NetworkChecker {
val isOnline: Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package os.dtakac.feritraspored.common.network

import android.content.Context
import android.net.ConnectivityManager

class NetworkCheckerImpl(
private val applicationContext: Context
) : NetworkChecker {
override val isOnline: Boolean
get() {
val connectivityManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE)
as? ConnectivityManager
@Suppress("DEPRECATION")
return connectivityManager?.activeNetworkInfo?.isConnectedOrConnecting == true
}
}
Loading

0 comments on commit 4e8d342

Please sign in to comment.