Add replying to messages (#263)

This commit is contained in:
Kacper Ziubryniewicz 2019-03-17 21:02:41 +01:00 committed by Mikołaj Pich
parent 824ed3f282
commit ba76453e45
50 changed files with 676 additions and 434 deletions

View File

@ -77,7 +77,7 @@ play {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation('com.github.wulkanowy:api:29d24ca1ff') { exclude module: "threetenbp" }
implementation('com.github.wulkanowy:api:a8d05df1ab') { exclude module: "threetenbp" }
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.0.2"
@ -107,7 +107,7 @@ dependencies {
implementation "com.aurelhubert:ahbottomnavigation:2.3.4"
implementation 'com.ncapdevi:frag-nav:3.1.0'
implementation "com.hootsuite.android:nachos:1.1.1"
implementation 'com.github.wulkanowy:MaterialChipsInput:b72fd0ee6f'
implementation 'com.github.PhilJay:MPAndroidChart:971640b29d'
implementation 'com.github.pwittchen:reactivenetwork-rx2:3.0.2'

View File

@ -21,7 +21,8 @@ class GradeLocalTest {
@Before
fun createDb() {
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java).build()
testDb = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), AppDatabase::class.java)
.build()
gradeLocal = GradeLocal(testDb.gradeDao)
}

View File

@ -7,9 +7,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.api.toDate
import io.github.wulkanowy.data.db.AppDatabase
import io.github.wulkanowy.data.db.entities.Grade
import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.repositories.TestInternetObservingStrategy
@ -22,7 +20,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDate.of
import org.threeten.bp.LocalDateTime
import kotlin.test.assertFalse

View File

@ -39,7 +39,7 @@ class GradeStatisticsLocalTest {
))
val stats = gradeStatisticsLocal.getGradesStatistics(
Semester(2, 2, "", 1, 2, true, 1 ,1), false,
Semester(2, 2, "", 1, 2, true, 1, 1), false,
"Matematyka"
).blockingGet()
assertEquals(1, stats.size)

View File

@ -39,7 +39,18 @@
android:name=".ui.modules.main.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/main_title"
android:launchMode="singleTop"
android:theme="@style/WulkanowyTheme.NoActionBar" />
<activity
android:name=".ui.modules.message.send.SendMessageActivity"
android:configChanges="orientation|screenSize"
android:label="@string/send_message_title"
android:parentActivityName=".ui.modules.main.MainActivity"
android:theme="@style/WulkanowyTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.modules.main.MainActivity" />
</activity>
<service
android:name=".services.widgets.TimetableWidgetService"

View File

@ -40,6 +40,7 @@ import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student
import io.github.wulkanowy.data.db.entities.Subject
import io.github.wulkanowy.data.db.entities.Timetable
import io.github.wulkanowy.data.db.migrations.Migration10
import io.github.wulkanowy.data.db.migrations.Migration2
import io.github.wulkanowy.data.db.migrations.Migration3
import io.github.wulkanowy.data.db.migrations.Migration4
@ -47,6 +48,7 @@ import io.github.wulkanowy.data.db.migrations.Migration5
import io.github.wulkanowy.data.db.migrations.Migration6
import io.github.wulkanowy.data.db.migrations.Migration7
import io.github.wulkanowy.data.db.migrations.Migration8
import io.github.wulkanowy.data.db.migrations.Migration9
import javax.inject.Singleton
@Singleton
@ -77,7 +79,7 @@ import javax.inject.Singleton
abstract class AppDatabase : RoomDatabase() {
companion object {
const val VERSION_SCHEMA = 8
const val VERSION_SCHEMA = 10
fun newInstance(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, "wulkanowy_database")
@ -91,7 +93,9 @@ abstract class AppDatabase : RoomDatabase() {
Migration5(),
Migration6(),
Migration7(),
Migration8()
Migration8(),
Migration9(),
Migration10()
)
.build()
}

View File

@ -18,6 +18,6 @@ interface GradeSummaryDao {
@Delete
fun deleteAll(gradesSummary: List<GradeSummary>)
@Query("SELECT * FROM grades_summary WHERE student_id = :studentId AND semester_id = :semesterId")
@Query("SELECT * FROM GradesSummary WHERE student_id = :studentId AND semester_id = :semesterId")
fun loadAll(semesterId: Int, studentId: Int): Maybe<List<GradeSummary>>
}

View File

@ -20,7 +20,7 @@ interface MessagesDao {
@Update
fun updateAll(messages: List<Message>)
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder ORDER BY date DESC")
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND folder_id = :folder AND removed = 0 ORDER BY date DESC")
fun loadAll(studentId: Int, folder: Int): Maybe<List<Message>>
@Query("SELECT * FROM Messages WHERE student_id = :studentId AND real_id = :id")

View File

@ -4,7 +4,7 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "Grades_Summary")
@Entity(tableName = "GradesSummary")
data class GradeSummary(
@ColumnInfo(name = "semester_id")

View File

@ -24,9 +24,6 @@ data class Message(
@ColumnInfo(name = "sender_id")
val senderId: Int,
@ColumnInfo(name = "recipient_id")
val recipientId: Int,
@ColumnInfo(name = "recipient_name")
val recipient: String,
@ -39,8 +36,10 @@ data class Message(
var unread: Boolean,
@ColumnInfo(name = "unread_by")
val unreadBy: Int,
@ColumnInfo(name = "read_by")
val readBy: Int,
val removed: Boolean

View File

@ -0,0 +1,11 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration10 : Migration(9, 10) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Grades_Summary RENAME TO GradesSummary")
}
}

View File

@ -6,11 +6,13 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration2 : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE LuckyNumbers (" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"is_notified INTEGER NOT NULL, " +
"student_id INTEGER NOT NULL, " +
"date INTEGER NOT NULL, " +
"lucky_number INTEGER NOT NULL)")
database.execSQL("""
CREATE TABLE IF NOT EXISTS LuckyNumbers (
id INTEGER PRIMARY KEY NOT NULL,
is_notified INTEGER NOT NULL,
student_id INTEGER NOT NULL,
date INTEGER NOT NULL,
lucky_number INTEGER NOT NULL)
""")
}
}

View File

@ -6,18 +6,20 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration3 : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE CompletedLesson (" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"student_id INTEGER NOT NULL, " +
"diary_id INTEGER NOT NULL, " +
"date INTEGER NOT NULL, " +
"number INTEGER NOT NULL, " +
"subject TEXT NOT NULL, " +
"topic TEXT NOT NULL, " +
"teacher TEXT NOT NULL, " +
"teacher_symbol TEXT NOT NULL, " +
"substitution TEXT NOT NULL, " +
"absence TEXT NOT NULL, " +
"resources TEXT NOT NULL)")
database.execSQL("""
CREATE TABLE IF NOT EXISTS CompletedLesson (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
diary_id INTEGER NOT NULL,
date INTEGER NOT NULL,
number INTEGER NOT NULL,
subject TEXT NOT NULL,
topic TEXT NOT NULL,
teacher TEXT NOT NULL,
teacher_symbol TEXT NOT NULL,
substitution TEXT NOT NULL,
absence TEXT NOT NULL,
resources TEXT NOT NULL)
""")
}
}

View File

@ -6,24 +6,26 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration4 : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS `Messages`")
database.execSQL("CREATE TABLE IF NOT EXISTS `Messages` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
"`is_notified` INTEGER NOT NULL," +
"`content` TEXT," +
"`student_id` INTEGER NOT NULL," +
"`real_id` INTEGER NOT NULL," +
"`message_id` INTEGER NOT NULL," +
"`sender_name` TEXT NOT NULL," +
"`sender_id` INTEGER NOT NULL," +
"`recipient_id` INTEGER NOT NULL," +
"`recipient_name` TEXT NOT NULL," +
"`subject` TEXT NOT NULL," +
"`date` INTEGER NOT NULL," +
"`folder_id` INTEGER NOT NULL," +
"`unread` INTEGER NOT NULL," +
"`unreadBy` INTEGER NOT NULL," +
"`readBy` INTEGER NOT NULL," +
"`removed` INTEGER NOT NULL)")
database.execSQL("DROP TABLE IF EXISTS Messages")
database.execSQL("""
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER PRIMARY KEY NOT NULL,
is_notified INTEGER NOT NULL,
content TEXT,
student_id INTEGER NOT NULL,
real_id INTEGER NOT NULL,
message_id INTEGER NOT NULL,
sender_name TEXT NOT NULL,
sender_id INTEGER NOT NULL,
recipient_id INTEGER NOT NULL,
recipient_name TEXT NOT NULL,
subject TEXT NOT NULL,
date INTEGER NOT NULL,
folder_id INTEGER NOT NULL,
unread INTEGER NOT NULL,
unreadBy INTEGER NOT NULL,
readBy INTEGER NOT NULL,
removed INTEGER NOT NULL)
""")
}
}

View File

@ -8,17 +8,19 @@ import org.threeten.bp.ZoneOffset
class Migration5 : Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Students ADD COLUMN `registration_date` INTEGER NOT NULL DEFAULT 0")
database.execSQL("UPDATE Students SET `registration_date` = '${now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()}'")
database.execSQL("DROP TABLE IF EXISTS `Notes`")
database.execSQL("CREATE TABLE IF NOT EXISTS `Notes` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
"`is_read` INTEGER NOT NULL," +
"`is_notified` INTEGER NOT NULL," +
"`student_id` INTEGER NOT NULL," +
"`date` INTEGER NOT NULL," +
"`teacher` TEXT NOT NULL," +
"`category` TEXT NOT NULL," +
"`content` TEXT NOT NULL)")
database.execSQL("ALTER TABLE Students ADD COLUMN registration_date INTEGER DEFAULT 0 NOT NULL")
database.execSQL("UPDATE Students SET registration_date = '${now().atZone(ZoneOffset.UTC).toInstant().toEpochMilli()}'")
database.execSQL("DROP TABLE IF EXISTS Notes")
database.execSQL("""
CREATE TABLE IF NOT EXISTS Notes (
id INTEGER PRIMARY KEY NOT NULL,
is_read INTEGER NOT NULL,
is_notified INTEGER NOT NULL,
student_id INTEGER NOT NULL,
date INTEGER NOT NULL,
teacher TEXT NOT NULL,
category TEXT NOT NULL,
content TEXT NOT NULL)
""")
}
}

View File

@ -6,25 +6,29 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration6 : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE ReportingUnits (" +
"id INTEGER NOT NULL PRIMARY KEY," +
"student_id INTEGER NOT NULL," +
"real_id INTEGER NOT NULL," +
"short TEXT NOT NULL," +
"sender_id INTEGER NOT NULL," +
"sender_name TEXT NOT NULL," +
"roles TEXT NOT NULL)")
database.execSQL("""
CREATE TABLE IF NOT EXISTS ReportingUnits (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
real_id INTEGER NOT NULL,
short TEXT NOT NULL,
sender_id INTEGER NOT NULL,
sender_name TEXT NOT NULL,
roles TEXT NOT NULL)
""")
database.execSQL("CREATE TABLE Recipients (" +
"id INTEGER NOT NULL PRIMARY KEY," +
"student_id INTEGER NOT NULL," +
"real_id TEXT NOT NULL," +
"name TEXT NOT NULL," +
"real_name TEXT NOT NULL," +
"login_id INTEGER NOT NULL," +
"unit_id INTEGER NOT NULL," +
"role INTEGER NOT NULL," +
"hash TEXT NOT NULL)")
database.execSQL("""
CREATE TABLE IF NOT EXISTS Recipients (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
real_id TEXT NOT NULL,
name TEXT NOT NULL,
real_name TEXT NOT NULL,
login_id INTEGER NOT NULL,
unit_id INTEGER NOT NULL,
role INTEGER NOT NULL,
hash TEXT NOT NULL)
""")
database.execSQL("DELETE FROM Semesters WHERE 1")
database.execSQL("ALTER TABLE Semesters ADD COLUMN class_id INTEGER DEFAULT 0 NOT NULL")

View File

@ -6,13 +6,15 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration7 : Migration(6, 7) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE IF NOT EXISTS `GradesStatistics` (" +
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
"`student_id` INTEGER NOT NULL," +
"`semester_id` INTEGER NOT NULL," +
"`subject` TEXT NOT NULL," +
"`grade` INTEGER NOT NULL," +
"`amount` INTEGER NOT NULL," +
"`is_semester` INTEGER NOT NULL)")
database.execSQL("""
CREATE TABLE IF NOT EXISTS GradesStatistics (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
semester_id INTEGER NOT NULL,
subject TEXT NOT NULL,
grade INTEGER NOT NULL,
amount INTEGER NOT NULL,
is_semester INTEGER NOT NULL)
""")
}
}

View File

@ -6,8 +6,8 @@ import androidx.sqlite.db.SupportSQLiteDatabase
class Migration8 : Migration(7, 8) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `Timetable` ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL")
database.execSQL("ALTER TABLE `Timetable` ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL")
database.execSQL("ALTER TABLE `Timetable` ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL")
database.execSQL("ALTER TABLE Timetable ADD COLUMN subjectOld TEXT DEFAULT \"\" NOT NULL")
database.execSQL("ALTER TABLE Timetable ADD COLUMN roomOld TEXT DEFAULT \"\" NOT NULL")
database.execSQL("ALTER TABLE Timetable ADD COLUMN teacherOld TEXT DEFAULT \"\" NOT NULL")
}
}

View File

@ -0,0 +1,30 @@
package io.github.wulkanowy.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration9 : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("DROP TABLE IF EXISTS Messages")
database.execSQL("""
CREATE TABLE IF NOT EXISTS Messages (
id INTEGER PRIMARY KEY NOT NULL,
student_id INTEGER NOT NULL,
real_id INTEGER NOT NULL,
message_id INTEGER NOT NULL,
sender_name TEXT NOT NULL,
sender_id INTEGER NOT NULL,
recipient_name TEXT NOT NULL,
subject TEXT NOT NULL,
date INTEGER NOT NULL,
folder_id INTEGER NOT NULL,
unread INTEGER NOT NULL,
unread_by INTEGER NOT NULL,
read_by INTEGER NOT NULL,
removed INTEGER NOT NULL,
is_notified INTEGER NOT NULL,
content TEXT)
""")
}
}

View File

@ -27,7 +27,8 @@ class GradeRepository @Inject constructor(
}.flatMap { newGrades ->
local.getGrades(semester).toSingle(emptyList())
.doOnSuccess { oldGrades ->
val notifyBreakDate = oldGrades.maxBy { it.date }?.date ?: student.registrationDate.toLocalDate()
val notifyBreakDate = oldGrades.maxBy { it.date }?.date
?: student.registrationDate.toLocalDate()
local.deleteGrades(oldGrades - newGrades)
local.saveGrades((newGrades - oldGrades)
.onEach {

View File

@ -20,6 +20,7 @@ class HomeworkLocal @Inject constructor(private val homeworkDb: HomeworkDao) {
}
fun getHomework(semester: Semester, startDate: LocalDate, endDate: LocalDate): Maybe<List<Homework>> {
return homeworkDb.loadAll(semester.semesterId, semester.studentId, startDate, endDate).filter { it.isNotEmpty() }
return homeworkDb.loadAll(semester.semesterId, semester.studentId, startDate, endDate)
.filter { it.isNotEmpty() }
}
}

View File

@ -26,7 +26,6 @@ class MessageRemote @Inject constructor(private val api: Api) {
sender = it.sender.orEmpty(),
senderId = it.senderId ?: 0,
recipient = it.recipient.orEmpty(),
recipientId = 0,
subject = it.subject.trim(),
date = it.date?.toLocalDateTime() ?: now(),
folderId = it.folderId,

View File

@ -83,6 +83,10 @@ class MessageRepository @Inject constructor(
}
fun sendMessage(subject: String, content: String, recipients: List<Recipient>): Single<SentMessage> {
return remote.sendMessage(subject, content, recipients)
return ReactiveNetwork.checkInternetConnectivity(settings)
.flatMap {
if (it) remote.sendMessage(subject, content, recipients)
else Single.error(UnknownHostException())
}
}
}

View File

@ -24,12 +24,6 @@ class PreferencesRepository @Inject constructor(
val currentTheme: Int
get() = sharedPref.getString(currentThemeKey, "1")?.toIntOrNull() ?: 1
val gradePlusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_plus), "0.0")?.toDoubleOrNull() ?: 0.0
val gradeMinusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_minus), "0.0")?.toDoubleOrNull() ?: 0.0
val gradeColorTheme: String
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_color_scheme), "vulcan") ?: "vulcan"
@ -51,4 +45,14 @@ class PreferencesRepository @Inject constructor(
val isDebugNotificationEnableKey: String = context.getString(R.string.pref_key_notification_debug)
val isDebugNotificationEnable: Boolean
get() = sharedPref.getBoolean(isDebugNotificationEnableKey, false)
val gradePlusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_plus), "0.0")?.toDouble() ?: 0.0
val gradeMinusModifier: Double
get() = sharedPref.getString(context.getString(R.string.pref_key_grade_modifier_minus), "0.0")?.toDouble()
?: 0.0
val fillMessageContent: Boolean
get() = sharedPref.getBoolean(context.getString(R.string.pref_key_fill_message_content), true)
}

View File

@ -1,30 +1,41 @@
package io.github.wulkanowy.data.repositories.recipient
import io.github.wulkanowy.api.Api
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Singleton
import io.github.wulkanowy.api.messages.Recipient as ApiRecipient
@Singleton
class RecipientRemote @Inject constructor(private val api: Api) {
fun getRecipients(role: Int, unit: ReportingUnit): Single<List<Recipient>> {
return api.getRecipients(role, unit.realId)
return api.getRecipients(unit.realId, role)
.map { recipients ->
recipients.map {
Recipient(
studentId = api.studentId,
name = it.name,
realName = it.name,
realId = it.id,
hash = it.hash,
loginId = it.loginId,
role = it.role,
unitId = it.reportingUnitId ?: 0
)
}
recipients.map { it.toRecipient() }
}
}
fun getMessageRecipients(message: Message): Single<List<Recipient>> {
return api.getMessageRecipients(message.messageId, message.senderId)
.map { recipients ->
recipients.map { it.toRecipient() }
}
}
private fun ApiRecipient.toRecipient(): Recipient {
return Recipient(
studentId = api.studentId,
realId = id,
realName = name,
name = shortName,
hash = hash,
loginId = loginId,
role = role,
unitId = reportingUnitId ?: 0
)
}
}

View File

@ -3,6 +3,7 @@ package io.github.wulkanowy.data.repositories.recipient
import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
import com.github.pwittchen.reactivenetwork.library.rx2.internet.observing.InternetObservingSettings
import io.github.wulkanowy.data.ApiHelper
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.db.entities.Student
@ -39,4 +40,13 @@ class RecipientRepository @Inject constructor(
)
}
}
fun getMessageRecipients(student: Student, message: Message): Single<List<Recipient>> {
return Single.just(apiHelper.initApi(student))
.flatMap { ReactiveNetwork.checkInternetConnectivity(settings) }
.flatMap {
if (it) remote.getMessageRecipients(message)
else Single.error(UnknownHostException())
}
}
}

View File

@ -8,6 +8,7 @@ import io.github.wulkanowy.ui.modules.login.LoginActivity
import io.github.wulkanowy.ui.modules.login.LoginModule
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainModule
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.splash.SplashActivity
import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetProvider
@ -15,7 +16,7 @@ import io.github.wulkanowy.ui.widgets.timetable.TimetableWidgetProvider
internal abstract class BuilderModule {
@PerActivity
@ContributesAndroidInjector()
@ContributesAndroidInjector
abstract fun bindSplashActivity(): SplashActivity
@PerActivity
@ -26,6 +27,9 @@ internal abstract class BuilderModule {
@ContributesAndroidInjector(modules = [MainModule::class])
abstract fun bindMainActivity(): MainActivity
@ContributesAndroidInjector
abstract fun bindMessageSendActivity(): SendMessageActivity
@ContributesAndroidInjector
abstract fun bindTimetableWidgetService(): TimetableWidgetService

View File

@ -20,7 +20,6 @@ import io.github.wulkanowy.ui.modules.luckynumber.LuckyNumberFragment
import io.github.wulkanowy.ui.modules.message.MessageFragment
import io.github.wulkanowy.ui.modules.message.MessageModule
import io.github.wulkanowy.ui.modules.message.preview.MessagePreviewFragment
import io.github.wulkanowy.ui.modules.message.send.SendMessageFragment
import io.github.wulkanowy.ui.modules.more.MoreFragment
import io.github.wulkanowy.ui.modules.note.NoteFragment
import io.github.wulkanowy.ui.modules.settings.SettingsFragment
@ -97,10 +96,6 @@ abstract class MainModule {
@ContributesAndroidInjector
abstract fun bindCompletedLessonsFragment(): CompletedLessonsFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindSendMessageFragment(): SendMessageFragment
@PerFragment
@ContributesAndroidInjector
abstract fun bindAccountDialog(): AccountDialog

View File

@ -12,9 +12,8 @@ import io.github.wulkanowy.data.repositories.message.MessageFolder.SENT
import io.github.wulkanowy.data.repositories.message.MessageFolder.TRASHED
import io.github.wulkanowy.ui.base.BaseFragment
import io.github.wulkanowy.ui.base.BaseFragmentPagerAdapter
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.send.SendMessageFragment
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import io.github.wulkanowy.ui.modules.message.tab.MessageTabFragment
import io.github.wulkanowy.utils.setOnSelectPageListener
import kotlinx.android.synthetic.main.fragment_message.*
@ -85,7 +84,7 @@ class MessageFragment : BaseFragment(), MessageView, MainView.TitledView {
}
override fun openSendMessage() {
(activity as? MainActivity)?.pushView(SendMessageFragment.newInstance())
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it)) }
}
override fun onDestroyView() {

View File

@ -1,20 +1,31 @@
package io.github.wulkanowy.ui.modules.message.preview
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.ui.modules.message.send.SendMessageActivity
import kotlinx.android.synthetic.main.fragment_message_preview.*
import javax.inject.Inject
@SuppressLint("SetTextI18n")
class MessagePreviewFragment : BaseSessionFragment(), MessagePreviewView, MainView.TitledView {
@Inject
lateinit var presenter: MessagePreviewPresenter
private var menuReplyButton: MenuItem? = null
override val titleStringId: Int
get() = R.string.message_title
@ -31,42 +42,66 @@ class MessagePreviewFragment : BaseSessionFragment(), MessagePreviewView, MainVi
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_message_preview, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
messageContainer = message
messageContainer = messagePreviewContainer
presenter.onAttachView(this, (savedInstanceState ?: arguments)?.getInt(MESSAGE_ID_KEY) ?: 0)
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater?.inflate(R.menu.action_menu_message_preview, menu)
menuReplyButton = menu?.findItem(R.id.messagePreviewMenuReply)
presenter.onCreateOptionsMenu()
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.messagePreviewMenuReply) presenter.onReply()
else false
}
override fun setSubject(subject: String) {
messageSubject.text = subject
messagePreviewSubject.text = subject
}
override fun setRecipient(recipient: String?) {
messageAuthor.text = getString(R.string.message_to, recipient)
override fun setRecipient(recipient: String) {
messagePreviewAuthor.text = "${getString(R.string.message_to)} $recipient"
}
override fun setSender(sender: String?) {
messageAuthor.text = getString(R.string.message_from, sender)
override fun setSender(sender: String) {
messagePreviewAuthor.text = "${getString(R.string.message_from)} $sender"
}
override fun setDate(date: String?) {
messageDate.text = getString(R.string.message_date, date)
override fun setDate(date: String) {
messagePreviewDate.text = getString(R.string.message_date, date)
}
override fun setContent(content: String?) {
messageContent.text = content
override fun setContent(content: String) {
messagePreviewContent.text = content
}
override fun showProgress(show: Boolean) {
messageProgress.visibility = if (show) View.VISIBLE else View.GONE
messagePreviewProgress.visibility = if (show) VISIBLE else GONE
}
override fun showReplyButton(show: Boolean) {
menuReplyButton?.isVisible = show
}
override fun showMessageError() {
messageError.visibility = View.VISIBLE
messagePreviewError.visibility = VISIBLE
}
override fun openMessageReply(message: Message?) {
context?.let { it.startActivity(SendMessageActivity.getStartIntent(it, message)) }
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -1,6 +1,7 @@
package io.github.wulkanowy.ui.modules.message.preview
import com.google.firebase.analytics.FirebaseAnalytics.Param.START_DATE
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
@ -21,6 +22,8 @@ class MessagePreviewPresenter @Inject constructor(
var messageId: Int = 0
private var replyMessage: Message? = null
fun onAttachView(view: MessagePreviewView, id: Int) {
super.onAttachView(view)
loadData(id)
@ -38,11 +41,13 @@ class MessagePreviewPresenter @Inject constructor(
.doFinally { view?.showProgress(false) }
.subscribe({ message ->
Timber.i("Loading message $id preview result: Success ")
replyMessage = message
view?.run {
message.let {
setSubject(if (it.subject.isNotBlank()) it.subject else noSubjectString)
setDate(it.date.toFormattedString("yyyy-MM-dd HH:mm:ss"))
setContent(it.content)
setContent(it.content.orEmpty())
showReplyButton(true)
if (it.recipient.isNotBlank()) setRecipient(it.recipient)
else setSender(it.sender)
@ -56,4 +61,15 @@ class MessagePreviewPresenter @Inject constructor(
})
}
}
fun onReply(): Boolean {
return if (replyMessage != null) {
view?.openMessageReply(replyMessage)
true
} else false
}
fun onCreateOptionsMenu() {
view?.showReplyButton(replyMessage != null)
}
}

View File

@ -1,5 +1,6 @@
package io.github.wulkanowy.ui.modules.message.preview
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.ui.base.session.BaseSessionView
interface MessagePreviewView : BaseSessionView {
@ -8,15 +9,19 @@ interface MessagePreviewView : BaseSessionView {
fun setSubject(subject: String)
fun setRecipient(recipient: String?)
fun setRecipient(recipient: String)
fun setSender(sender: String?)
fun setSender(sender: String)
fun setDate(date: String?)
fun setDate(date: String)
fun setContent(content: String?)
fun setContent(content: String)
fun showProgress(show: Boolean)
fun showReplyButton(show: Boolean)
fun showMessageError()
fun openMessageReply(message: Message?)
}

View File

@ -0,0 +1,30 @@
package io.github.wulkanowy.ui.modules.message.send
import android.graphics.drawable.Drawable
import android.net.Uri
import com.pchmn.materialchips.model.ChipInterface
import io.github.wulkanowy.data.db.entities.Recipient
class RecipientChip(var recipient: Recipient) : ChipInterface {
override fun getAvatarDrawable(): Drawable? = null
override fun getAvatarUri(): Uri? = null
override fun getId(): Any = recipient.id
override fun getLabel(): String = recipient.name
override fun getInfo(): String {
return recipient.realName.run {
substringBeforeLast("-").let { sub ->
when {
(sub == this) -> this
(sub.indexOf('(') != -1) -> indexOf("(").let { substring(if (it != -1) it else 0) }
(sub.indexOf('[') != -1) -> indexOf("[").let { substring(if (it != -1) it else 0) }
else -> substringAfter('-')
}
}.trim()
}
}
}

View File

@ -0,0 +1,127 @@
package io.github.wulkanowy.ui.modules.message.send
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.ui.base.BaseActivity
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.showSoftInput
import kotlinx.android.synthetic.main.activity_send_message.*
import javax.inject.Inject
class SendMessageActivity : BaseActivity(), SendMessageView {
@Inject
lateinit var presenter: SendMessagePresenter
companion object {
private const val EXTRA_MESSAGE = "EXTRA_MESSAGE"
fun getStartIntent(context: Context) = Intent(context, SendMessageActivity::class.java)
fun getStartIntent(context: Context, message: Message?): Intent {
return getStartIntent(context).putExtra(EXTRA_MESSAGE, message)
}
}
override val formRecipientsData: List<Recipient>
get() = (sendMessageRecipientsInput.selectedChipList).map { (it as RecipientChip).recipient }
override val formSubjectValue: String
get() = sendMessageSubjectInput.text.toString()
override val formContentValue: String
get() = sendMessageContentInput.text.toString()
override val messageRequiredRecipients: String
get() = getString(R.string.message_required_recipients)
override val messageContentMinLength: String
get() = getString(R.string.message_content_min_length)
override val messageSuccess: String
get() = getString(R.string.message_send_successful)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_send_message)
setSupportActionBar(sendMessageToolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
messageContainer = sendMessageContainer
presenter.onAttachView(this, intent.getSerializableExtra(EXTRA_MESSAGE) as? Message)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_menu_send_message, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.sendMessageMenuSend) presenter.onSend()
else false
}
override fun setReportingUnit(unit: ReportingUnit) {
sendMessageFromTextView.setText(unit.senderName)
}
override fun setRecipients(recipients: List<Recipient>) {
sendMessageRecipientsInput.filterableList = recipients.map { RecipientChip(it) }
}
override fun setSelectedRecipients(recipients: List<Recipient>) {
recipients.map { sendMessageRecipientsInput.addChip(RecipientChip(it)) }
}
override fun showProgress(show: Boolean) {
sendMessageProgress.visibility = if (show) VISIBLE else GONE
}
override fun showContent(show: Boolean) {
sendMessageContent.visibility = if (show) VISIBLE else GONE
}
override fun showEmpty(show: Boolean) {
sendMessageEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showActionBar(show: Boolean) {
supportActionBar?.apply { if (show) show() else hide() }
}
override fun setSubject(subject: String) {
sendMessageSubjectInput.setText(subject)
}
override fun setContent(content: String) {
sendMessageContentInput.setText(content)
}
override fun showMessage(text: String) {
Toast.makeText(this, text, LENGTH_LONG).show()
}
override fun showSoftInput(show: Boolean) {
if (show) showSoftInput() else hideSoftInput()
}
override fun popView() {
onBackPressed()
}
override fun onDestroy() {
presenter.onDetachView()
super.onDestroy()
}
}

View File

@ -1,154 +0,0 @@
package io.github.wulkanowy.ui.modules.message.send
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.content.ContextCompat
import com.hootsuite.nachos.ChipConfiguration
import com.hootsuite.nachos.chip.ChipSpan
import com.hootsuite.nachos.chip.ChipSpanChipCreator
import com.hootsuite.nachos.tokenizer.SpanChipTokenizer
import io.github.wulkanowy.R
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.ui.base.session.BaseSessionFragment
import io.github.wulkanowy.ui.modules.main.MainActivity
import io.github.wulkanowy.ui.modules.main.MainView
import io.github.wulkanowy.utils.hideSoftInput
import io.github.wulkanowy.utils.setOnTextChangedListener
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_send_message.*
import javax.inject.Inject
class SendMessageFragment : BaseSessionFragment(), SendMessageView, MainView.TitledView {
@Inject
lateinit var presenter: SendMessagePresenter
private var recipients: List<Recipient> = emptyList()
private lateinit var recipientsAdapter: ArrayAdapter<Recipient>
companion object {
fun newInstance() = SendMessageFragment()
}
override val titleStringId: Int
get() = R.string.send_message_title
override val formRecipientsData: List<Recipient>
get() = sendMessageRecipientInput.allChips.map { it.data as Recipient }
override val formSubjectValue: String
get() = sendMessageSubjectInput.text.toString()
override val formContentValue: String
get() = sendMessageContentInput.text.toString()
override val messageRequiredRecipients: String
get() = getString(R.string.send_message_required_recipients)
override val messageContentMinLength: String
get() = getString(R.string.send_message_content_min_length)
override val messageSuccess: String
get() = getString(R.string.send_message_successful)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_send_message, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.onAttachView(this)
}
override fun initView() {
context?.let {
sendMessageRecipientInput.chipTokenizer = SpanChipTokenizer<ChipSpan>(it, object : ChipSpanChipCreator() {
override fun createChip(context: Context, text: CharSequence, data: Any?): ChipSpan {
return ChipSpan(context, text, ContextCompat.getDrawable(context, R.drawable.ic_all_account_24dp), data)
}
override fun configureChip(chip: ChipSpan, chipConfiguration: ChipConfiguration) {
super.configureChip(chip, chipConfiguration)
chip.setShowIconOnLeft(true)
}
}, ChipSpan::class.java)
recipientsAdapter = ArrayAdapter(it, android.R.layout.simple_dropdown_item_1line)
}
sendMessageRecipientInput.setAdapter(recipientsAdapter)
sendMessageRecipientInput.setOnTextChangedListener { presenter.onTypingRecipients() }
}
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
inflater?.inflate(R.menu.action_menu_send_message, menu)
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
return if (item?.itemId == R.id.sendMessageMenuSend) presenter.onSend()
else false
}
override fun setReportingUnit(unit: ReportingUnit) {
sendMessageFromTextView.setText(unit.senderName)
}
override fun setRecipients(recipients: List<Recipient>) {
this.recipients = recipients
}
override fun refreshRecipientsAdapter() {
recipientsAdapter.run {
clear()
addAll(recipients - sendMessageRecipientInput.allChips.map { it.data as Recipient })
notifyDataSetChanged()
}
}
override fun showProgress(show: Boolean) {
sendMessageProgress.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showContent(show: Boolean) {
sendMessageContent.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showEmpty(show: Boolean) {
sendMessageEmpty.visibility = if (show) View.VISIBLE else View.GONE
}
override fun popView() {
(activity as? MainActivity)?.popView()
}
override fun hideSoftInput() {
activity?.hideSoftInput()
}
override fun showBottomNav(show: Boolean) {
(activity as? MainActivity)?.mainBottomNav?.visibility = if (show) View.VISIBLE else View.GONE
}
override fun showMessage(text: String) {
Toast.makeText(context, text, Toast.LENGTH_LONG).show()
}
override fun onDestroyView() {
super.onDestroyView()
presenter.onDetachView()
}
}

View File

@ -1,50 +1,77 @@
package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.db.entities.Message
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.data.repositories.message.MessageRepository
import io.github.wulkanowy.data.repositories.preferences.PreferencesRepository
import io.github.wulkanowy.data.repositories.recipient.RecipientRepository
import io.github.wulkanowy.data.repositories.reportingunit.ReportingUnitRepository
import io.github.wulkanowy.data.repositories.semester.SemesterRepository
import io.github.wulkanowy.data.repositories.student.StudentRepository
import io.github.wulkanowy.ui.base.session.BaseSessionPresenter
import io.github.wulkanowy.ui.base.session.SessionErrorHandler
import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.FirebaseAnalyticsHelper
import io.github.wulkanowy.utils.SchedulersProvider
import io.github.wulkanowy.utils.toFormattedString
import io.reactivex.Completable
import timber.log.Timber
import javax.inject.Inject
class SendMessagePresenter @Inject constructor(
private val errorHandler: SessionErrorHandler,
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersProvider,
private val studentRepository: StudentRepository,
private val semesterRepository: SemesterRepository,
private val messageRepository: MessageRepository,
private val reportingUnitRepository: ReportingUnitRepository,
private val recipientRepository: RecipientRepository,
private val preferencesRepository: PreferencesRepository,
private val analytics: FirebaseAnalyticsHelper
) : BaseSessionPresenter<SendMessageView>(errorHandler) {
) : BasePresenter<SendMessageView>(errorHandler) {
private lateinit var reportingUnit: ReportingUnit
override fun onAttachView(view: SendMessageView) {
fun onAttachView(view: SendMessageView, message: Message?) {
super.onAttachView(view)
Timber.i("Send message view is attached")
view.run {
initView()
showBottomNav(false)
loadData(message)
view.apply {
message?.let {
setSubject("RE: ${message.subject}")
if (preferencesRepository.fillMessageContent) {
setContent(when (message.sender.isNotEmpty()) {
true -> "\n\nOd: ${message.sender}\n"
false -> "\n\nDo: ${message.recipient}\n"
} + "Data: ${message.date.toFormattedString("yyyy-MM-dd HH:mm:ss")}\n\n${message.content}")
}
}
}
loadRecipients()
}
private fun loadRecipients() {
private fun loadData(message: Message?) {
var reportingUnit: ReportingUnit? = null
var recipients: List<Recipient> = emptyList()
var selectedRecipient: List<Recipient> = emptyList()
Timber.i("Loading recipients started")
disposable.add(studentRepository.getCurrentStudent()
.flatMapMaybe { student ->
semesterRepository.getCurrentSemester(student)
.flatMapMaybe { reportingUnitRepository.getReportingUnit(student, it.unitId) }
.flatMap { semesterRepository.getCurrentSemester(it).map { semester -> it to semester } }
.flatMapCompletable { (student, semester) ->
reportingUnitRepository.getReportingUnit(student, semester.unitId)
.doOnSuccess { reportingUnit = it }
.flatMap { recipientRepository.getRecipients(student, 2, it).toMaybe() }
.doOnSuccess {
Timber.i("Loading recipients result: Success, fetched %d recipients", it.size)
recipients = it
}
.flatMapCompletable {
if (message == null) Completable.complete()
else recipientRepository.getMessageRecipients(student, message)
.doOnSuccess {
Timber.i("Loaded message recipients to reply result: Success, fetched %d recipients", it.size)
selectedRecipient = it
}
.ignoreElement()
}
}
.subscribeOn(schedulers.backgroundThread)
.observeOn(schedulers.mainThread)
@ -54,28 +81,24 @@ class SendMessagePresenter @Inject constructor(
showContent(false)
}
}
.doFinally {
view?.run {
showProgress(false)
}
}
.doFinally { view?.run { showProgress(false) } }
.subscribe({
Timber.i("Loading recipients result: Success, fetched %s recipients", it.size.toString())
view?.apply {
setReportingUnit(reportingUnit)
setRecipients(it)
refreshRecipientsAdapter()
showContent(true)
if (reportingUnit !== null) {
reportingUnit?.let { setReportingUnit(it) }
setRecipients(recipients)
if (selectedRecipient.isNotEmpty()) setSelectedRecipients(selectedRecipient)
showContent(true)
} else {
Timber.e("Loading recipients result: Can't find the reporting unit")
view?.showEmpty(true)
}
}
}, {
Timber.i("Loading recipients result: An exception occurred")
Timber.e("Loading recipients result: An exception occurred")
view?.showContent(true)
errorHandler.dispatch(it)
}, {
Timber.i("Loading recipients result: Can't find the reporting unit")
view?.showEmpty(true)
})
)
}))
}
private fun sendMessage(subject: String, content: String, recipients: List<Recipient>) {
@ -85,14 +108,12 @@ class SendMessagePresenter @Inject constructor(
.observeOn(schedulers.mainThread)
.doOnSubscribe {
view?.run {
hideSoftInput()
showSoftInput(false)
showContent(false)
showProgress(true)
showActionBar(false)
}
}
.doFinally {
view?.showProgress(false)
}
.subscribe({
Timber.i("Sending message result: Success")
analytics.logEvent("send_message", "recipients" to recipients.size)
@ -102,16 +123,16 @@ class SendMessagePresenter @Inject constructor(
}
}, {
Timber.i("Sending message result: An exception occurred")
view?.showContent(true)
view?.run {
showContent(true)
showProgress(false)
showActionBar(true)
}
errorHandler.dispatch(it)
})
)
}
fun onTypingRecipients() {
view?.refreshRecipientsAdapter()
}
fun onSend(): Boolean {
view?.run {
when {
@ -129,9 +150,4 @@ class SendMessagePresenter @Inject constructor(
}
return false
}
override fun onDetachView() {
view?.showBottomNav(true)
super.onDetachView()
}
}

View File

@ -2,9 +2,9 @@ package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.db.entities.Recipient
import io.github.wulkanowy.data.db.entities.ReportingUnit
import io.github.wulkanowy.ui.base.session.BaseSessionView
import io.github.wulkanowy.ui.base.BaseView
interface SendMessageView : BaseSessionView {
interface SendMessageView : BaseView {
val formRecipientsData: List<Recipient>
@ -18,13 +18,11 @@ interface SendMessageView : BaseSessionView {
val messageSuccess: String
fun initView()
fun setReportingUnit(unit: ReportingUnit)
fun setRecipients(recipients: List<Recipient>)
fun refreshRecipientsAdapter()
fun setSelectedRecipients(recipients: List<Recipient>)
fun showProgress(show: Boolean)
@ -32,9 +30,13 @@ interface SendMessageView : BaseSessionView {
fun showEmpty(show: Boolean)
fun showActionBar(show: Boolean)
fun setSubject(subject: String)
fun setContent(content: String)
fun showSoftInput(show: Boolean)
fun popView()
fun hideSoftInput()
fun showBottomNav(show: Boolean)
}

View File

@ -57,7 +57,7 @@ class MessageTabFragment : BaseSessionFragment(), MessageTabView, MessageView.Me
super.onActivityCreated(savedInstanceState)
messageContainer = messageTabRecycler
presenter.onAttachView(this, MessageFolder.valueOf(
(savedInstanceState ?: arguments)?.getString(MessageTabFragment.MESSAGE_TAB_FOLDER_ID) ?: ""
(savedInstanceState ?: arguments)?.getString(MessageTabFragment.MESSAGE_TAB_FOLDER_ID).orEmpty()
))
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z" />
</vector>

View File

@ -3,13 +3,28 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/sendMessageContainer"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/sendMessageAppBarContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/WulkanowyTheme.ActionBar"
app:elevation="0dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/sendMessageToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:id="@+id/sendMessageContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/sendMessageAppBarContainer"
android:orientation="vertical">
<LinearLayout
@ -22,14 +37,14 @@
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:text="@string/send_message_from"
android:text="@string/message_from"
android:textColor="@android:color/darker_gray"
android:textSize="18sp" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/sendMessageFromTextView"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_height="30dp"
android:background="@android:color/transparent"
android:enabled="false"
android:textColor="?android:attr/textColorPrimaryNoDisable"
@ -43,32 +58,32 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="58dp"
android:paddingStart="14dp"
android:paddingLeft="14dp"
android:paddingTop="14dp"
android:paddingEnd="0dp"
android:paddingRight="0dp"
android:paddingBottom="14dp">
tools:ignore="RtlSymmetry">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
android:text="@string/send_message_to"
android:text="@string/message_to"
android:textAlignment="center"
android:textColor="@android:color/darker_gray"
android:textSize="18sp" />
<com.hootsuite.nachos.NachoTextView
android:id="@+id/sendMessageRecipientInput"
<com.pchmn.materialchips.ChipsInput
android:id="@+id/sendMessageRecipientsInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/transparent"
android:inputType="text"
android:maxLines="1"
android:textColor="?attr/chipTextColor"
app:chipBackground="?attr/chipBackgroundColor" />
app:horizontalScroll="true"
tools:layout_height="30dp">
</com.pchmn.materialchips.ChipsInput>
</LinearLayout>
<View
@ -79,9 +94,9 @@
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/sendMessageSubjectInput"
android:layout_width="match_parent"
android:layout_height="66dp"
android:layout_height="58dp"
android:background="@android:color/transparent"
android:hint="@string/send_message_subject"
android:hint="@string/message_subject"
android:inputType="text"
android:maxLines="1"
android:padding="14dp" />
@ -97,7 +112,7 @@
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:gravity="top|start"
android:hint="@string/send_message_content"
android:hint="@string/message_content"
android:inputType="textMultiLine"
android:paddingStart="14dp"
android:paddingLeft="14dp"
@ -107,9 +122,10 @@
android:singleLine="false" />
</LinearLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:layout_below="@id/sendMessageAppBarContainer">
<LinearLayout
android:id="@+id/sendMessageEmpty"
@ -141,5 +157,5 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</FrameLayout>
</RelativeLayout>

View File

@ -1,22 +1,22 @@
<FrameLayout 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"
android:id="@+id/messagePreviewContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/messageSubject"
android:id="@+id/messagePreviewSubject"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
@ -25,7 +25,7 @@
tools:text="@tools:sample/lorem" />
<TextView
android:id="@+id/messageAuthor"
android:id="@+id/messagePreviewAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
@ -34,7 +34,7 @@
tools:text="@tools:sample/full_names" />
<TextView
android:id="@+id/messageDate"
android:id="@+id/messagePreviewDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="15dp"
@ -43,7 +43,7 @@
tools:text="@tools:sample/date/ddmmyy" />
<TextView
android:id="@+id/messageContent"
android:id="@+id/messagePreviewContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
@ -51,11 +51,10 @@
android:textIsSelectable="true"
tools:text="@tools:sample/lorem" />
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>
<LinearLayout
android:id="@+id/messageError"
android:id="@+id/messagePreviewError"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
@ -82,7 +81,7 @@
</LinearLayout>
<ProgressBar
android:id="@+id/messageProgress"
android:id="@+id/messagePreviewProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/messagePreviewMenuReply"
android:icon="@drawable/ic_message_reply_24dp"
android:orderInCategory="1"
android:title="@string/message_reply"
app:showAsAction="ifRoom" />
</menu>

View File

@ -7,9 +7,12 @@
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
<item name="bottomNavBackground">@color/bottom_nav_background</item>
<item name="android:navigationBarColor" tools:targetApi="21">@color/bottom_nav_background</item>
<item name="chipBackgroundColor">@color/chip_material_background_inverse</item>
<item name="chipTextColor">@color/chip_default_text_color_inverse</item>
<item name="dividerColor">@color/divider_inverse</item>
<item name="chip_backgroundColor">@color/chip_backgroundColor_inverse</item>
<item name="chip_labelColor">@color/chip_labelColor_inverse</item>
<item name="chip_detailed_backgroundColor">@color/chip_backgroundColor_inverse</item>
<item name="filterable_list_backgroundColor">@color/filterable_list_backgroundColor_inverse</item>
<item name="filterable_list_textColor">@color/filterable_list_textColor_inverse</item>
<!--AboutLibraries specific values-->
<item name="about_libraries_window_background">@color/about_libraries_window_background_dark</item>

View File

@ -13,6 +13,7 @@
<string name="more_title">Więcej</string>
<string name="about_title">O aplikacji</string>
<string name="message_title">Wiadomości</string>
<string name="send_message_title">Nowa wiadomość</string>
<string name="note_title">Uwagi i osiągnięcia</string>
<string name="homework_title">Zadania domowe</string>
<string name="account_title">Wybierz konto</string>
@ -135,9 +136,15 @@
<string name="message_no_subject">(brak tematu)</string>
<string name="message_no_items">Brak wiadomości</string>
<string name="message_preview_error">Wystąpił błąd podczas pobierania treści wiadomości</string>
<string name="message_from">Od: %s</string>
<string name="message_to">Do: %s</string>
<string name="message_from">Od:</string>
<string name="message_to">Do:</string>
<string name="message_date">Data: %s</string>
<string name="message_reply">Odpowiedz</string>
<string name="message_subject">Temat</string>
<string name="message_content">Treść</string>
<string name="message_send_successful">Wiadomość wysłana pomyślnie</string>
<string name="message_required_recipients">Musisz wybrać co najmniej 1 adresata</string>
<string name="message_content_min_length">Treść wiadomości musi zawierać co najmniej 3 znaki</string>
<plurals name="message_number_item">
<item quantity="one">%d wiadomość</item>
<item quantity="few">%d wiadomości</item>
@ -154,16 +161,6 @@
<item quantity="many">Dostałeś %1$d wiadomości</item>
</plurals>
<!--Send message-->
<string name="send_message_title">Wyślij</string>
<string name="send_message_from">Od:</string>
<string name="send_message_to">Do:</string>
<string name="send_message_subject">Temat</string>
<string name="send_message_content">Treść</string>
<string name="send_message_successful">Wiadomość wysłana pomyślnie</string>
<string name="send_message_required_recipients">Musisz wybrać co najmniej 1 adresata</string>
<string name="send_message_content_min_length">Treść wiadomości musi zawierać co najmniej 3 znaki</string>
<!--Note-->
<string name="note_no_items">Brak informacji o uwagach</string>
@ -241,8 +238,6 @@
<string name="pref_view_summary">Pokazuj podsumowanie w ocenach</string>
<string name="pref_view_present">Pokazuj obecność we frekwencji</string>
<string name="pref_view_theme_dark">Ciemny motyw (Beta)</string>
<string name="pref_view_grade_modifier_plus">Wartość plusa</string>
<string name="pref_view_grade_modifier_minus">Wartość minusa</string>
<string name="pref_view_expand_grade">Rozwiń oceny</string>
<string name="pref_grade_color_scheme">Schemat kolorów ocen</string>
@ -256,6 +251,11 @@
<string name="pref_services_interval">Interwał aktualizacji</string>
<string name="pref_services_wifi">Tylko WiFi</string>
<string name="pref_other_header">Inne</string>
<string name="pref_other_grade_modifier_plus">Wartość plusa</string>
<string name="pref_other_grade_modifier_minus">Wartość minusa</string>
<string name="pref_other_fill_message_content">Odpowiadaj z historią wiadomości</string>
<!--Notification Channels-->
<string name="channel_new_entries">Nowe wpisy w dzienniku</string>

View File

@ -32,8 +32,11 @@
<color name="bottom_nav_background">#303030</color>
<color name="bottom_nav_background_inverse">#ffffff</color>
<color name="chip_material_background_inverse">#595959</color>
<color name="chip_default_text_color_inverse">#fefefe</color>
<color name="chip_backgroundColor_inverse">#595959</color>
<color name="chip_labelColor_inverse">#fefefe</color>
<color name="chip_detailed_backgroundColor">#fefefe</color>
<color name="filterable_list_backgroundColor_inverse">#393939</color>
<color name="filterable_list_textColor_inverse">#fefefe</color>
<color name="divider">#cccccc</color>
<color name="divider_inverse">#777777</color>

View File

@ -3,8 +3,6 @@
<string name="pref_key_start_menu">start_menu</string>
<string name="pref_key_attendance_present">attendance_present</string>
<string name="pref_key_theme">theme</string>
<string name="pref_key_grade_modifier_plus">grade_modifier_plus</string>
<string name="pref_key_grade_modifier_minus">grade_modifier_minus</string>
<string name="pref_key_grade_color_scheme">grade_color_scheme</string>
<string name="pref_key_expand_grade">expand_grade</string>
<string name="pref_key_services_enable">services_enable</string>
@ -12,4 +10,7 @@
<string name="pref_key_services_wifi_only">services_disable_wifi_only</string>
<string name="pref_key_notifications_enable">notifications_enable</string>
<string name="pref_key_notification_debug">notification_debug</string>
<string name="pref_key_grade_modifier_plus">grade_modifier_plus</string>
<string name="pref_key_grade_modifier_minus">grade_modifier_minus</string>
<string name="pref_key_fill_message_content">fill_message_content</string>
</resources>

View File

@ -13,6 +13,7 @@
<string name="more_title">More</string>
<string name="about_title">About</string>
<string name="message_title">Messages</string>
<string name="send_message_title">New message</string>
<string name="note_title">Notes and achievements</string>
<string name="homework_title">Homework</string>
<string name="account_title">Choose account</string>
@ -129,9 +130,15 @@
<string name="message_no_subject">(no subject)</string>
<string name="message_no_items">No messages</string>
<string name="message_preview_error">An error occurred while downloading message content</string>
<string name="message_from">From: %s</string>
<string name="message_to">To: %s</string>
<string name="message_from">From:</string>
<string name="message_to">To:</string>
<string name="message_date">Date: %s</string>
<string name="message_reply">Reply</string>
<string name="message_subject">Subject</string>
<string name="message_content">Content</string>
<string name="message_send_successful">Message sent successfully</string>
<string name="message_required_recipients">You need to choose at least 1 recipient</string>
<string name="message_content_min_length">The message content must be at least 3 characters</string>
<plurals name="message_number_item">
<item quantity="one">%d message</item>
<item quantity="other">%d messages</item>
@ -145,16 +152,6 @@
<item quantity="other">You received %1$d messages</item>
</plurals>
<!--Send message-->
<string name="send_message_title">Send</string>
<string name="send_message_from">From:</string>
<string name="send_message_to">To:</string>
<string name="send_message_subject">Subject</string>
<string name="send_message_content">Content</string>
<string name="send_message_successful">Message sent successfully</string>
<string name="send_message_required_recipients">You need to choose at least 1 recipient</string>
<string name="send_message_content_min_length">The message content must be at least 3 characters</string>
<!--Note-->
<string name="note_no_items">No info about notes</string>
@ -226,8 +223,6 @@
<string name="pref_view_summary">Show the summary in the grades</string>
<string name="pref_view_present">Show presence in attendance</string>
<string name="pref_view_theme_dark">Dark theme (Beta)</string>
<string name="pref_view_grade_modifier_plus">Value of the plus</string>
<string name="pref_view_grade_modifier_minus">Value of the minus</string>
<string name="pref_view_expand_grade">Expand grades</string>
<string name="pref_grade_color_scheme">Grades color scheme</string>
@ -241,6 +236,11 @@
<string name="pref_services_interval">Updates interval</string>
<string name="pref_services_wifi">Wi-Fi only</string>
<string name="pref_other_header">Other</string>
<string name="pref_other_grade_modifier_plus">Value of the plus</string>
<string name="pref_other_grade_modifier_minus">Value of the minus</string>
<string name="pref_other_fill_message_content">Reply with message history</string>
<!--Notification Channels-->
<string name="channel_new_entries">New entries in register</string>

View File

@ -14,9 +14,8 @@
<item name="subtitleTextColor">@android:color/primary_text_dark</item>
<item name="android:colorBackground">@android:color/white</item>
<item name="bottomNavBackground">@color/bottom_nav_background_inverse</item>
<item name="chipBackgroundColor">@color/chip_material_background</item>
<item name="chipTextColor">@color/chip_default_text_color</item>
<item name="dividerColor">@color/divider</item>
<item name="chip_detailed_backgroundColor">@color/chip_detailed_backgroundColor</item>
<!-- AboutLibraries specific values -->
<item name="about_libraries_window_background">@color/about_libraries_window_background</item>

View File

@ -38,22 +38,6 @@
android:summary="%s"
android:title="@string/pref_grade_color_scheme"
app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="0.0"
android:entries="@array/grade_modifier_entries"
android:entryValues="@array/grade_modifier_value"
android:key="@string/pref_key_grade_modifier_plus"
android:summary="%s"
android:title="@string/pref_view_grade_modifier_plus"
app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="0.0"
android:entries="@array/grade_modifier_entries"
android:entryValues="@array/grade_modifier_value"
android:key="@string/pref_key_grade_modifier_minus"
android:summary="%s"
android:title="@string/pref_view_grade_modifier_minus"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_services_header"
@ -94,4 +78,29 @@
android:title="@string/pref_notify_debug_switch"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/pref_other_header"
app:iconSpaceReserved="false">
<ListPreference
android:defaultValue="0.0"
android:entries="@array/grade_modifier_entries"
android:entryValues="@array/grade_modifier_value"
android:key="@string/pref_key_grade_modifier_plus"
android:summary="%s"
android:title="@string/pref_other_grade_modifier_plus"
app:iconSpaceReserved="false" />
<ListPreference
android:defaultValue="0.0"
android:entries="@array/grade_modifier_entries"
android:entryValues="@array/grade_modifier_value"
android:key="@string/pref_key_grade_modifier_minus"
android:summary="%s"
android:title="@string/pref_other_grade_modifier_minus"
app:iconSpaceReserved="false" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/pref_key_fill_message_content"
android:title="@string/pref_other_fill_message_content"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</PreferenceScreen>

View File

@ -0,0 +1,20 @@
package io.github.wulkanowy.ui.modules.message.send
import io.github.wulkanowy.data.db.entities.Recipient
import org.junit.Assert.assertEquals
import org.junit.Test
class TestRecipientChip {
@Test
fun testRecipientChipInfo() {
assertEquals("Uczeń", getRecipientChip("Kowalski Jan - Uczeń").info)
assertEquals("(JK) - pracownik [Fake123456]", getRecipientChip("Kowalski Jan (JK) - pracownik [Fake123456]").info)
assertEquals("[KK] - pracownik (Fake123456)", getRecipientChip("Kowalska Karolina [KK] - pracownik (Fake123456)").info)
assertEquals("(BK) - Nauczyciel [Fake123456]", getRecipientChip("Kowal-Mazur Barbara (BK) - Nauczyciel [Fake123456]").info)
}
private fun getRecipientChip(realName: String): RecipientChip {
return RecipientChip(Recipient(0, "", "", realName, 0, 0, 0, ""))
}
}