diff --git a/app/build.gradle b/app/build.gradle index f034b276..0121d93a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -188,6 +188,8 @@ dependencies { implementation "com.squareup.retrofit2:converter-gson:${versions.retrofit}" implementation 'com.github.jetradarmobile:android-snowfall:1.2.0' + + implementation "io.coil-kt:coil:0.9.2" } repositories { mavenCentral() diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/FeedbackMessageEvent.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/FeedbackMessageEvent.kt new file mode 100644 index 00000000..aeb3ac0c --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/events/FeedbackMessageEvent.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-21. + */ + +package pl.szczodrzynski.edziennik.data.api.events + +import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage + +data class FeedbackMessageEvent(val message: FeedbackMessage) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt index 7427d9e7..7846aae4 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyApi.kt @@ -17,6 +17,7 @@ import pl.szczodrzynski.edziennik.data.api.szkolny.response.ApiResponse import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.api.szkolny.response.WebPushResponse import pl.szczodrzynski.edziennik.data.db.entity.Event +import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage import pl.szczodrzynski.edziennik.data.db.entity.Notification import pl.szczodrzynski.edziennik.data.db.entity.Profile import pl.szczodrzynski.edziennik.data.db.full.EventFull @@ -214,4 +215,14 @@ class SzkolnyApi(val app: App) { fun getUpdate(channel: String): ApiResponse>? { return api.updates(channel).execute().body() } + + fun sendFeedbackMessage(senderName: String?, targetDeviceId: String?, text: String): FeedbackMessage? { + return api.feedbackMessage(FeedbackMessageRequest( + deviceId = app.deviceId, + device = getDevice(), + senderName = senderName, + targetDeviceId = targetDeviceId, + text = text + )).execute().body()?.data?.message + } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt index ea5ac8f1..4b5bda6f 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/SzkolnyService.kt @@ -5,10 +5,7 @@ package pl.szczodrzynski.edziennik.data.api.szkolny 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.ServerSyncResponse -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 retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET @@ -34,4 +31,7 @@ interface SzkolnyService { @GET("updates/app") fun updates(@Query("channel") channel: String = "release"): Call>> + + @POST("feedbackMessage") + fun feedbackMessage(@Body request: FeedbackMessageRequest): Call> } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/FeedbackMessageRequest.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/FeedbackMessageRequest.kt new file mode 100644 index 00000000..9548e0fd --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/request/FeedbackMessageRequest.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-21. + */ + +package pl.szczodrzynski.edziennik.data.api.szkolny.request + +data class FeedbackMessageRequest( + val deviceId: String, + val device: Device? = null, + + val senderName: String?, + val targetDeviceId: String?, + val text: String +) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/FeedbackMessageResponse.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/FeedbackMessageResponse.kt new file mode 100644 index 00000000..0ca526fb --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/api/szkolny/response/FeedbackMessageResponse.kt @@ -0,0 +1,9 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-21. + */ + +package pl.szczodrzynski.edziennik.data.api.szkolny.response + +import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage + +data class FeedbackMessageResponse(val message: FeedbackMessage) diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt index 06bdc578..f875a117 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/AppDb.kt @@ -42,7 +42,7 @@ import pl.szczodrzynski.edziennik.data.db.migration.* ConfigEntry::class, LibrusLesson::class, Metadata::class -], version = 75) +], version = 76) @TypeConverters( ConverterTime::class, ConverterDate::class, @@ -158,7 +158,8 @@ abstract class AppDb : RoomDatabase() { Migration72(), Migration73(), Migration74(), - Migration75() + Migration75(), + Migration76() ).allowMainThreadQueries().build() } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/FeedbackMessageDao.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/FeedbackMessageDao.java deleted file mode 100644 index 0aa5e8ba..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/FeedbackMessageDao.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.dao; - -import java.util.List; - -import androidx.lifecycle.LiveData; -import androidx.room.Dao; -import androidx.room.Insert; -import androidx.room.OnConflictStrategy; -import androidx.room.Query; - -import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage; -import pl.szczodrzynski.edziennik.data.db.full.FeedbackMessageWithCount; - -@Dao -public interface FeedbackMessageDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - void add(FeedbackMessage feedbackMessage); - - @Insert(onConflict = OnConflictStrategy.REPLACE) - void addAll(List feedbackMessageList); - - @Query("DELETE FROM feedbackMessages") - void clear(); - - @Query("SELECT * FROM feedbackMessages") - List getAllNow(); - - @Query("SELECT * FROM feedbackMessages WHERE fromUser = :fromUser") - List getAllByUserNow(String fromUser); - - @Query("SELECT * FROM feedbackMessages") - LiveData> getAll(); - - @Query("SELECT *, COUNT(*) AS messageCount FROM feedbackMessages GROUP BY fromUser ORDER BY sentTime DESC") - List getAllWithCountNow(); -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/FeedbackMessageDao.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/FeedbackMessageDao.kt new file mode 100644 index 00000000..7b3159ae --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/dao/FeedbackMessageDao.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-6 + */ +package pl.szczodrzynski.edziennik.data.db.dao + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage + +@Dao +interface FeedbackMessageDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun add(feedbackMessage: FeedbackMessage) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addAll(feedbackMessageList: List) + + @Query("DELETE FROM feedbackMessages") + fun clear() + + @get:Query("SELECT * FROM feedbackMessages ORDER BY sentTime DESC LIMIT 50") + val allNow: List + + @Query("SELECT * FROM feedbackMessages WHERE deviceId = :deviceId ORDER BY sentTime DESC LIMIT 50") + fun getByDeviceIdNow(deviceId: String): List + + @get:Query("SELECT * FROM feedbackMessages") + val all: LiveData> + + @get:Query("SELECT *, COUNT(*) AS count FROM feedbackMessages WHERE received = 1 AND devId IS NULL AND deviceId != 'szkolny.eu' GROUP BY deviceId ORDER BY sentTime DESC") + val allWithCountNow: List +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/FeedbackMessage.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/FeedbackMessage.java deleted file mode 100644 index 718f75f4..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/FeedbackMessage.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.entity; - -import androidx.room.Entity; -import androidx.room.Ignore; -import androidx.room.PrimaryKey; - -@Entity(tableName = "feedbackMessages") -public class FeedbackMessage { - - @PrimaryKey(autoGenerate = true) - public int messageId; - - public boolean received = false; - public String fromUser = null; - public String fromUserName = null; - public long sentTime = System.currentTimeMillis(); - public String text; - - public FeedbackMessage(boolean received, String text) { - this.received = received; - this.sentTime = System.currentTimeMillis(); - this.text = text; - } - - @Ignore - public FeedbackMessage() { - } -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/FeedbackMessage.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/FeedbackMessage.kt new file mode 100644 index 00000000..8ceb41f7 --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/entity/FeedbackMessage.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) Kuba Szczodrzyński 2020-1-21. + */ + +package pl.szczodrzynski.edziennik.data.db.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "feedbackMessages") +open class FeedbackMessage( + @PrimaryKey(autoGenerate = true) + val messageId: Int = 0, + + val received: Boolean, + val text: String, + + // used always - contains the sender name + val senderName: String, + + // used in DEV apps - contains device ID and model + var deviceId: String? = null, + var deviceName: String? = null, + + val devId: Int? = null, + val devImage: String? = null, + + val sentTime: Long = System.currentTimeMillis() +) { + class WithCount(messageId: Int, received: Boolean, text: String, senderName: String, deviceId: String?, deviceName: String?, devId: Int?, devImage: String?, sentTime: Long) : FeedbackMessage(messageId, received, text, senderName, deviceId, deviceName, devId, devImage, sentTime) { + var count = 0 + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/FeedbackMessageWithCount.java b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/FeedbackMessageWithCount.java deleted file mode 100644 index 26d9b4b9..00000000 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/full/FeedbackMessageWithCount.java +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (c) Kacper Ziubryniewicz 2020-1-6 - */ - -package pl.szczodrzynski.edziennik.data.db.full; - -import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage; - -public class FeedbackMessageWithCount extends FeedbackMessage { - public int messageCount = 0; -} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration76.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration76.kt new file mode 100644 index 00000000..4faa02bc --- /dev/null +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/db/migration/Migration76.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) Kacper Ziubryniewicz 2020-1-25 + */ + +package pl.szczodrzynski.edziennik.data.db.migration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration76 : Migration(75, 76) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("ALTER TABLE feedbackMessages RENAME TO _feedbackMessages;") + database.execSQL("CREATE TABLE feedbackMessages (\n" + + "\tmessageId INTEGER NOT NULL PRIMARY KEY,\n" + + "\treceived INTEGER NOT NULL,\n" + + "\ttext TEXT NOT NULL,\n" + + "\tsenderName TEXT NOT NULL,\n" + + "\tdeviceId TEXT DEFAULT NULL,\n" + + "\tdeviceName TEXT DEFAULT NULL,\n" + + "\tdevId INTEGER DEFAULT NULL,\n" + + "\tdevImage TEXT DEFAULT NULL,\n" + + "\tsentTime INTEGER NOT NULL\n" + + ");") + database.execSQL("INSERT INTO feedbackMessages (messageId, received, text, senderName, deviceId, deviceName, devId, devImage, sentTime)\n" + + "SELECT messageId, received, text,\n" + + "CASE fromUser IS NOT NULL WHEN 1 THEN CASE fromUserName IS NULL WHEN 1 THEN \"\" ELSE fromUserName END ELSE \"\" END AS senderName,\n" + + "fromUser AS deviceId,\n" + + "NULL AS deviceName,\n" + + "CASE received AND fromUser IS NULL WHEN 1 THEN 100 ELSE NULL END AS devId,\n" + + "NULL AS devImage,\n" + + "sentTime\n" + + "FROM _feedbackMessages;") + database.execSQL("DROP TABLE _feedbackMessages;") + } +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt index aa069d0f..78a1f688 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/data/firebase/SzkolnyAppFirebase.kt @@ -5,17 +5,13 @@ package pl.szczodrzynski.edziennik.data.firebase import com.google.gson.JsonParser -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus import pl.szczodrzynski.edziennik.* +import pl.szczodrzynski.edziennik.data.api.events.FeedbackMessageEvent import pl.szczodrzynski.edziennik.data.api.szkolny.response.Update import pl.szczodrzynski.edziennik.data.api.task.PostNotifications -import pl.szczodrzynski.edziennik.data.db.entity.Event -import pl.szczodrzynski.edziennik.data.db.entity.Metadata -import pl.szczodrzynski.edziennik.data.db.entity.Notification -import pl.szczodrzynski.edziennik.data.db.entity.Profile +import pl.szczodrzynski.edziennik.data.db.entity.* import pl.szczodrzynski.edziennik.sync.UpdateWorker import pl.szczodrzynski.edziennik.utils.models.Date import pl.szczodrzynski.edziennik.utils.models.Time @@ -49,7 +45,18 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: message.data.getString("title") ?: "", message.data.getString("message") ?: "" ) - "appUpdate" -> launch { UpdateWorker.runNow(app, app.gson.fromJson(message.data.get("update"), Update::class.java)) } + "appUpdate" -> launch { UpdateWorker.runNow(app, app.gson.fromJson(message.data.getString("update"), Update::class.java)) } + "feedbackMessage" -> launch { + val message = app.gson.fromJson(message.data.getString("message"), FeedbackMessage::class.java) + if (message.deviceId == app.deviceId) { + message.deviceId = null + message.deviceName = null + } + withContext(Dispatchers.Default) { + app.db.feedbackMessageDao().add(message) + } + EventBus.getDefault().postSticky(FeedbackMessageEvent(message)) + } } } } @@ -164,4 +171,4 @@ class SzkolnyAppFirebase(val app: App, val profiles: List, val message: app.db.notificationDao().addAll(notificationList) PostNotifications(app, notificationList) } -} \ No newline at end of file +} diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackActivity.java b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackActivity.java index c3704ce9..6f891d15 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackActivity.java +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackActivity.java @@ -1,180 +1,22 @@ package pl.szczodrzynski.edziennik.ui.modules.feedback; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.AsyncTask; import android.os.Bundle; import android.view.MenuItem; -import android.view.View; -import android.view.animation.Animation; -import android.widget.PopupMenu; -import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; -import com.afollestad.materialdialogs.MaterialDialog; -import com.github.bassaer.chatmessageview.model.IChatUser; -import com.github.bassaer.chatmessageview.model.Message; -import com.github.bassaer.chatmessageview.view.ChatView; - -import java.util.Calendar; -import java.util.List; - import pl.szczodrzynski.edziennik.App; import pl.szczodrzynski.edziennik.R; -import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage; -import pl.szczodrzynski.edziennik.data.db.full.FeedbackMessageWithCount; import pl.szczodrzynski.edziennik.databinding.ActivityFeedbackBinding; -import pl.szczodrzynski.edziennik.network.ServerRequest; -import pl.szczodrzynski.edziennik.utils.Anim; import pl.szczodrzynski.edziennik.utils.Themes; -import pl.szczodrzynski.edziennik.utils.Utils; - -import static pl.szczodrzynski.edziennik.utils.Utils.crc16; -import static pl.szczodrzynski.edziennik.utils.Utils.openUrl; public class FeedbackActivity extends AppCompatActivity { private static final String TAG = "FeedbackActivity"; private App app; private ActivityFeedbackBinding b; - private boolean firstSend = true; - private String deviceToSend = null; - private String nameToSend = null; - private BroadcastReceiver receiver; - - private class User implements IChatUser { - Integer id; - String name; - Bitmap icon; - - public User(int id, String name, Bitmap icon) { - this.id = id; - this.name = name; - this.icon = icon; - } - - @Override - public String getId() { - return this.id.toString(); - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Bitmap getIcon() { - return this.icon; - } - - @Override - public void setIcon(Bitmap icon) { - this.icon = icon; - } - } - - private User dev; - private User user; - private ChatView mChatView; - - private void send(String text){ - /*if ("enable dev mode pls".equals(text)) { - try { - Log.d(TAG, Utils.AESCrypt.encrypt("ok here you go it's enabled now", "8iryqZUfIUiLmJGi")); - } catch (Exception e) { - e.printStackTrace(); - } - return; - }*/ - MaterialDialog progressDialog = new MaterialDialog.Builder(this) - .title(R.string.loading) - .content(R.string.sending_message) - .negativeText(R.string.cancel) - .show(); - new ServerRequest(app, "https://edziennik.szczodrzynski.pl/app/main.php?feedback_message", "FeedbackSend") - .setBodyParameter("message_text", text) - .setBodyParameter("target_device", deviceToSend == null ? "null" : deviceToSend) - .run(((e, result) -> { - progressDialog.dismiss(); - if (result != null && result.get("success") != null && result.get("success").getAsBoolean()) { - FeedbackMessage feedbackMessage = new FeedbackMessage(false, text); - if (deviceToSend != null) { - feedbackMessage.fromUser = deviceToSend; - feedbackMessage.fromUserName = nameToSend; - } - AsyncTask.execute(() -> app.db.feedbackMessageDao().add(feedbackMessage)); - Message message = new Message.Builder() - .setUser(user) - .setRight(true) - .setText(feedbackMessage.text) - .hideIcon(true) - .build(); - mChatView.send(message); - mChatView.setInputText(""); - b.textInput.setText(""); - if (firstSend) { - Anim.fadeOut(b.inputLayout, 500, new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { - b.inputLayout.setVisibility(View.GONE); - Anim.fadeIn(b.chatLayout, 500, null); - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - }); - if (deviceToSend == null) { - // we are not the developer - FeedbackMessage feedbackMessage2 = new FeedbackMessage(true, "Postaram się jak najszybciej Tobie odpowiedzieć. Dostaniesz powiadomienie o odpowiedzi, która pokaże się w tym miejscu."); - AsyncTask.execute(() -> app.db.feedbackMessageDao().add(feedbackMessage2)); - message = new Message.Builder() - .setUser(dev) - .setRight(false) - .setText(feedbackMessage2.text) - .hideIcon(false) - .build(); - mChatView.receive(message); - } - firstSend = false; - } - } - else { - Toast.makeText(app, "Nie udało się wysłać wiadomości.", Toast.LENGTH_SHORT).show(); - } - })); - } - - private void openFaq() { - openUrl(this, "http://szkolny.eu/pomoc/"); - new MaterialDialog.Builder(this) - .title(R.string.faq_back_title) - .content(R.string.faq_back_text) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive(((dialog, which) -> { - - })) - .onNegative(((dialog, which) -> { - - })) - .show(); - } @Override protected void onCreate(Bundle savedInstanceState) { @@ -188,154 +30,6 @@ public class FeedbackActivity extends AppCompatActivity { if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); - b.faqText.setOnClickListener((v -> { - openFaq(); - })); - b.faqButton.setOnClickListener((v -> { - openFaq(); - })); - - receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - FeedbackMessage message = app.getGson().fromJson(intent.getStringExtra("message"), FeedbackMessage.class); - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(message.sentTime); - Message chatMessage = new Message.Builder() - .setUser(intent.getStringExtra("type").equals("dev_chat") ? new User(crc16(message.fromUser.getBytes()), message.fromUserName, BitmapFactory.decodeResource(getResources(), R.drawable.ic_account_circle)) : dev) - .setRight(!message.received) - .setText(message.text) - .setSendTime(c) - .hideIcon(!message.received) - .build(); - if (message.received) - mChatView.receive(chatMessage); - else - mChatView.send(chatMessage); - } - }; - - mChatView = b.chatView; - - dev = new User(0, "Szkolny.eu", BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); - user = new User(1, "Ja", BitmapFactory.decodeResource(getResources(), R.drawable.profile)); - - //Set UI parameters if you need - mChatView.setLeftBubbleColor(Utils.getAttr(this, R.attr.colorSurface)); - mChatView.setLeftMessageTextColor(Utils.getAttr(this, android.R.attr.textColorPrimary)); - mChatView.setRightBubbleColor(Utils.getAttr(this, R.attr.colorPrimary)); - mChatView.setRightMessageTextColor(Color.WHITE); - - //mChatView.setBackgroundColor(ContextCompat.getColor(this, R.color.blueGray500)); - mChatView.setSendButtonColor(Utils.getAttr(this, R.attr.colorAccent)); - mChatView.setSendIcon(R.drawable.ic_action_send); - //mChatView.setUsernameTextColor(Color.WHITE); - //mChatView.setSendTimeTextColor(Color.WHITE); - //mChatView.setDateSeparatorColor(Color.WHITE); - mChatView.setInputTextHint("Napisz..."); - //mChatView.setInputTextColor(Color.BLACK); - mChatView.setMessageMarginTop(5); - mChatView.setMessageMarginBottom(5); - - if (App.Companion.getDevMode() && app.getDeviceId().equals("f054761fbdb6a238")) { - b.targetDeviceLayout.setVisibility(View.VISIBLE); - b.targetDeviceDropDown.setOnClickListener((v -> { - AsyncTask.execute(() -> { - List messageList = app.db.feedbackMessageDao().getAllWithCountNow(); - runOnUiThread(() -> { - PopupMenu popupMenu = new PopupMenu(this, b.targetDeviceDropDown); - int index = 0; - for (FeedbackMessageWithCount message: messageList) { - popupMenu.getMenu().add(0, index, index, message.fromUserName+" - "+message.fromUser+" ("+message.messageCount+")"); - index++; - } - popupMenu.setOnMenuItemClickListener(item -> { - b.targetDeviceDropDown.setText(item.getTitle()); - mChatView.getMessageView().removeAll(); - FeedbackMessageWithCount message = messageList.get(item.getItemId()); - deviceToSend = message.fromUser; - nameToSend = message.fromUserName; - AsyncTask.execute(() -> { - List messageList2 = app.db.feedbackMessageDao().getAllByUserNow(deviceToSend); - runOnUiThread(() -> { - b.chatLayout.setVisibility(View.VISIBLE); - b.inputLayout.setVisibility(View.GONE); - for (FeedbackMessage message2 : messageList2) { - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(message2.sentTime); - Message chatMessage = new Message.Builder() - .setUser(message2.received ? new User(crc16(message2.fromUser.getBytes()), message2.fromUserName, BitmapFactory.decodeResource(getResources(), R.drawable.ic_account_circle)) : user) - .setRight(!message2.received) - .setText(message2.text) - .setSendTime(c) - .hideIcon(!message2.received) - .build(); - if (message2.received) - mChatView.receive(chatMessage); - else - mChatView.send(chatMessage); - } - }); - }); - return false; - }); - popupMenu.show(); - }); - }); - })); - } - else { - AsyncTask.execute(() -> { - List messageList = app.db.feedbackMessageDao().getAllNow(); - firstSend = messageList.size() == 0; - runOnUiThread(() -> { - if (firstSend) { - openFaq(); - b.chatLayout.setVisibility(View.GONE); - b.inputLayout.setVisibility(View.VISIBLE); - b.sendButton.setOnClickListener((v -> { - if (b.textInput.getText() == null || b.textInput.getText().length() == 0) { - Toast.makeText(app, "Podaj treść wiadomości.", Toast.LENGTH_SHORT).show(); - } else { - send(b.textInput.getText().toString()); - } - })); - } else { - /*new MaterialDialog.Builder(this) - .title(R.string.faq) - .content(R.string.faq_text) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive(((dialog, which) -> { - openFaq(); - })) - .show();*/ - b.chatLayout.setVisibility(View.VISIBLE); - b.inputLayout.setVisibility(View.GONE); - } - for (FeedbackMessage message : messageList) { - Calendar c = Calendar.getInstance(); - c.setTimeInMillis(message.sentTime); - Message chatMessage = new Message.Builder() - .setUser(message.fromUser != null ? new User(crc16(message.fromUser.getBytes()), message.fromUserName, BitmapFactory.decodeResource(getResources(), R.drawable.ic_account_circle)) : message.received ? dev : user) - .setRight(!message.received) - .setText(message.text) - .setSendTime(c) - .hideIcon(!message.received) - .build(); - if (message.received) - mChatView.receive(chatMessage); - else - mChatView.send(chatMessage); - } - }); - }); - } - - //Click Send Button - mChatView.setOnClickSendButtonListener(view -> { - send(mChatView.getInputText()); - }); } @Override @@ -351,12 +45,10 @@ public class FeedbackActivity extends AppCompatActivity { @Override protected void onResume() { super.onResume(); - registerReceiver(receiver, new IntentFilter("pl.szczodrzynski.edziennik.ui.modules.base.FeedbackActivity")); } @Override protected void onPause() { super.onPause(); - unregisterReceiver(receiver); } } diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackFragment.kt index ddba893e..5f4fe29e 100644 --- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackFragment.kt +++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/feedback/FeedbackFragment.kt @@ -1,333 +1,268 @@ package pl.szczodrzynski.edziennik.ui.modules.feedback import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.Canvas import android.graphics.Color -import android.os.AsyncTask import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.animation.Animation import android.widget.PopupMenu import android.widget.Toast import androidx.fragment.app.Fragment -import com.afollestad.materialdialogs.MaterialDialog +import coil.Coil +import coil.api.load import com.github.bassaer.chatmessageview.model.IChatUser import com.github.bassaer.chatmessageview.model.Message import com.github.bassaer.chatmessageview.view.ChatView +import kotlinx.coroutines.* +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode import pl.szczodrzynski.edziennik.App import pl.szczodrzynski.edziennik.MainActivity import pl.szczodrzynski.edziennik.R +import pl.szczodrzynski.edziennik.data.api.events.FeedbackMessageEvent +import pl.szczodrzynski.edziennik.data.api.szkolny.SzkolnyApi import pl.szczodrzynski.edziennik.data.db.entity.FeedbackMessage import pl.szczodrzynski.edziennik.databinding.FragmentFeedbackBinding -import pl.szczodrzynski.edziennik.network.ServerRequest -import pl.szczodrzynski.edziennik.utils.Anim +import pl.szczodrzynski.edziennik.onClick import pl.szczodrzynski.edziennik.utils.Themes import pl.szczodrzynski.edziennik.utils.Utils -import pl.szczodrzynski.edziennik.utils.Utils.crc16 import pl.szczodrzynski.edziennik.utils.Utils.openUrl import java.util.* +import kotlin.coroutines.CoroutineContext -class FeedbackFragment : Fragment() { +class FeedbackFragment : Fragment(), CoroutineScope { private lateinit var app: App private lateinit var activity: MainActivity private lateinit var b: FragmentFeedbackBinding + private val job: Job = Job() + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main + + private val chatView: ChatView by lazy { b.chatView } + private val api by lazy { SzkolnyApi(app) } + private var isDev = false + private var currentDeviceId: String? = null + + private var receiver: BroadcastReceiver? = null + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { activity = (getActivity() as MainActivity?) ?: return null if (context == null) return null app = activity.application as App context!!.theme.applyStyle(Themes.appTheme, true) - if (app.profile == null) - return inflater.inflate(R.layout.fragment_loading, container, false) // activity, context and profile is valid b = FragmentFeedbackBinding.inflate(inflater) return b.root } + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + fun onFeedbackMessageEvent(event: FeedbackMessageEvent) { + EventBus.getDefault().removeStickyEvent(event) + val message = event.message + val chatMessage = getChatMessage(message) + if (message.received) chatView.receive(chatMessage) + else chatView.send(chatMessage) + } + + private val users = mutableMapOf( + 0 to User(0, "Ja", null) + ) + private fun getUser(message: FeedbackMessage): User { + val userId = message.devId ?: if (message.received) 1 else 0 + return users[userId] ?: run { + User(userId, message.senderName, message.devImage).also { users[userId] = it } + } + } + + private fun getChatMessage(message: FeedbackMessage): Message = Message.Builder() + .setUser(getUser(message)) + .setRight(!message.received) + .setText(message.text) + .setSendTime(Calendar.getInstance().apply { timeInMillis = message.sentTime }) + .hideIcon(!message.received) + .build() + + private fun launchDeviceSelection() { + if (!isDev) + return + launch { + val messages = withContext(Dispatchers.Default) { app.db.feedbackMessageDao().allWithCountNow } + val popupMenu = PopupMenu(activity, b.targetDeviceDropDown) + messages.forEachIndexed { index, m -> + popupMenu.menu.add(0, index, index, "${m.senderName} ${m.deviceName} (${m.count})") + } + popupMenu.setOnMenuItemClickListener { item -> + b.targetDeviceDropDown.setText(item.title) + chatView.getMessageView().removeAll() + val message = messages[item.itemId] + currentDeviceId = message.deviceId + this@FeedbackFragment.launch { loadMessages() } + false + } + popupMenu.show() + } + } + + private suspend fun loadMessages(messageList: List? = null) { + /*if (messageList != null && messageList.isNotEmpty()) + return*/ + val messages = messageList ?: withContext(Dispatchers.Default) { + if (currentDeviceId == null) + app.db.feedbackMessageDao().allNow + else + app.db.feedbackMessageDao().getByDeviceIdNow(currentDeviceId!!) + } + + if (messages.isNotEmpty()) { + b.chatLayout.visibility = View.VISIBLE + b.inputLayout.visibility = View.GONE + } + + messages.forEach { + val chatMessage = getChatMessage(it) + if (it.received) chatView.receive(chatMessage) + else chatView.send(chatMessage) + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // TODO check if app, activity, b can be null - if (app.profile == null || !isAdded) + if (!isAdded) return - b.faqText.setOnClickListener { v -> openFaq() } - b.faqButton.setOnClickListener { v -> openFaq() } + b.faqText.setOnClickListener { openFaq() } + b.faqButton.setOnClickListener { openFaq() } - receiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val message = app.gson.fromJson(intent.getStringExtra("message"), FeedbackMessage::class.java) - val c = Calendar.getInstance() - c.timeInMillis = message.sentTime - val chatMessage = Message.Builder() - .setUser( - if (intent.getStringExtra("type") == "dev_chat") - User(crc16(message.fromUser.toByteArray()), message.fromUserName, BitmapFactory.decodeResource(resources, R.drawable.ic_account_circle)) - else - dev) - .setRight(!message.received) - .setText(message.text) - .setSendTime(c) - .hideIcon(!message.received) - .build() - if (message.received) - mChatView.receive(chatMessage) - else - mChatView.send(chatMessage) - } + with(chatView) { + setLeftBubbleColor(Utils.getAttr(activity, R.attr.colorSurface)) + setLeftMessageTextColor(Utils.getAttr(activity, android.R.attr.textColorPrimary)) + setRightBubbleColor(Utils.getAttr(activity, R.attr.colorPrimary)) + setRightMessageTextColor(Color.WHITE) + setSendButtonColor(Utils.getAttr(activity, R.attr.colorAccent)) + setSendIcon(R.drawable.ic_action_send) + setInputTextHint("Napisz...") + setMessageMarginTop(5) + setMessageMarginBottom(5) } - //Set UI parameters if you need - mChatView.setLeftBubbleColor(Utils.getAttr(activity, R.attr.colorSurface)) - mChatView.setLeftMessageTextColor(Utils.getAttr(activity, android.R.attr.textColorPrimary)) - mChatView.setRightBubbleColor(Utils.getAttr(activity, R.attr.colorPrimary)) - mChatView.setRightMessageTextColor(Color.WHITE) - - //mChatView.setBackgroundColor(ContextCompat.getColor(this, R.color.blueGray500)); - mChatView.setSendButtonColor(Utils.getAttr(activity, R.attr.colorAccent)) - mChatView.setSendIcon(R.drawable.ic_action_send) - //mChatView.setUsernameTextColor(Color.WHITE); - //mChatView.setSendTimeTextColor(Color.WHITE); - //mChatView.setDateSeparatorColor(Color.WHITE); - mChatView.setInputTextHint("Napisz...") - //mChatView.setInputTextColor(Color.BLACK); - mChatView.setMessageMarginTop(5) - mChatView.setMessageMarginBottom(5) - - if (App.devMode && app.deviceId == "f054761fbdb6a238") { - b.targetDeviceLayout.visibility = View.VISIBLE - b.targetDeviceDropDown.setOnClickListener { v -> - AsyncTask.execute { - val messageList = app.db.feedbackMessageDao().allWithCountNow - activity.runOnUiThread { - val popupMenu = PopupMenu(activity, b.targetDeviceDropDown) - var index = 0 - for (message in messageList) { - popupMenu.menu.add(0, index, index, message.fromUserName + " - " + message.fromUser + " (" + message.messageCount + ")") - index++ - } - popupMenu.setOnMenuItemClickListener { item -> - b.targetDeviceDropDown.setText(item.title) - mChatView.getMessageView().removeAll() - val message = messageList[item.itemId] - deviceToSend = message.fromUser - nameToSend = message.fromUserName - AsyncTask.execute { - val messageList2 = app.db.feedbackMessageDao().getAllByUserNow(deviceToSend) - activity.runOnUiThread { - b.chatLayout.visibility = View.VISIBLE - b.inputLayout.visibility = View.GONE - for (message2 in messageList2) { - val c = Calendar.getInstance() - c.timeInMillis = message2.sentTime - val chatMessage = Message.Builder() - .setUser(if (message2.received) User(crc16(message2.fromUser.toByteArray()), message2.fromUserName, BitmapFactory.decodeResource(resources, R.drawable.ic_account_circle)) else user) - .setRight(!message2.received) - .setText(message2.text) - .setSendTime(c) - .hideIcon(!message2.received) - .build() - if (message2.received) - mChatView.receive(chatMessage) - else - mChatView.send(chatMessage) - } - } - } - false - } - popupMenu.show() - } - } + launch { + val messages = withContext(Dispatchers.Default) { + val messages = app.db.feedbackMessageDao().allNow + isDev = App.devMode && messages.any { it.deviceId != null } + messages } - } else { - AsyncTask.execute { - val messageList = app.db.feedbackMessageDao().allNow - firstSend = messageList.size == 0 - activity.runOnUiThread { - if (firstSend) { - openFaq() - b.chatLayout.visibility = View.GONE - b.inputLayout.visibility = View.VISIBLE - b.sendButton.setOnClickListener { v -> - if (b.textInput.text == null || b.textInput.text!!.length == 0) { - Toast.makeText(app, "Podaj treść wiadomości.", Toast.LENGTH_SHORT).show() - } else { - send(b.textInput.text!!.toString()) - } - } - } else { - /*new MaterialDialog.Builder(this) - .title(R.string.faq) - .content(R.string.faq_text) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive(((dialog, which) -> { - openFaq(); - })) - .show();*/ - b.chatLayout.visibility = View.VISIBLE - b.inputLayout.visibility = View.GONE - } - for (message in messageList) { - val c = Calendar.getInstance() - c.timeInMillis = message.sentTime - val chatMessage = Message.Builder() - .setUser(if (message.fromUser != null) User(crc16(message.fromUser.toByteArray()), message.fromUserName, BitmapFactory.decodeResource(resources, R.drawable.ic_account_circle)) else if (message.received) dev else user) - .setRight(!message.received) - .setText(message.text) - .setSendTime(c) - .hideIcon(!message.received) - .build() - if (message.received) - mChatView.receive(chatMessage) - else - mChatView.send(chatMessage) - } - } + + b.targetDeviceLayout.visibility = if (isDev) View.VISIBLE else View.GONE + b.targetDeviceDropDown.onClick { + launchDeviceSelection() } + + if (isDev) { + messages.firstOrNull()?.let { + currentDeviceId = it.deviceId + b.targetDeviceDropDown.setText("${it.senderName} ${it.deviceName}") + } + b.chatLayout.visibility = View.VISIBLE + b.inputLayout.visibility = View.GONE + } + else if (messages.isEmpty()) { + b.chatLayout.visibility = View.GONE + b.inputLayout.visibility = View.VISIBLE + } + + loadMessages(messages) + + b.sendButton.onClick { + send(b.textInput.text.toString()) + } + chatView.setOnClickSendButtonListener(View.OnClickListener { + send(chatView.inputText) + }) } - - //Click Send Button - mChatView.setOnClickSendButtonListener(View.OnClickListener { send(mChatView.inputText) }) } - private var firstSend = true - private var deviceToSend: String? = null - private var nameToSend: String? = null - - private var receiver: BroadcastReceiver? = null - - class User(internal var id: Int?, internal var name: String, internal var icon: Bitmap) : IChatUser { - - override fun getId(): String { - return this.id!!.toString() - } - - override fun getName(): String? { - return this.name - } - + inner class User(val id: Int, val userName: String, val image: String?) : IChatUser { override fun getIcon(): Bitmap? { - return this.icon - } - - override fun setIcon(icon: Bitmap) { - this.icon = icon - } - } - - private val dev: User by lazy { - User(0, "Szkolny.eu", BitmapFactory.decodeResource(activity.resources, R.mipmap.ic_splash)) - } - private val user: User by lazy { - User(1, "Ja", BitmapFactory.decodeResource(activity.resources, R.drawable.profile_)) - } - private val mChatView: ChatView by lazy { - b.chatView - } - - private fun send(text: String) { - /*if ("enable dev mode pls".equals(text)) { - try { - Log.d(TAG, Utils.AESCrypt.encrypt("ok here you go it's enabled now", "8iryqZUfIUiLmJGi")); - } catch (Exception e) { - e.printStackTrace(); - } - return; - }*/ - val progressDialog = MaterialDialog.Builder(activity) - .title(R.string.loading) - .content(R.string.sending_message) - .negativeText(R.string.cancel) - .show() - ServerRequest(app, "https://edziennik.szczodrzynski.pl/app/main.php?feedback_message", "FeedbackSend") - .setBodyParameter("message_text", text) - .setBodyParameter("target_device", if (deviceToSend == null) "null" else deviceToSend) - .run { e, result -> - progressDialog.dismiss() - if (result != null && result.get("success") != null && result.get("success").asBoolean) { - val feedbackMessage = FeedbackMessage(false, text) - if (deviceToSend != null) { - feedbackMessage.fromUser = deviceToSend - feedbackMessage.fromUserName = nameToSend + if (image == null) + return BitmapFactory.decodeResource(activity.resources, R.drawable.profile_) + return Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888).also { bmp -> + launch { + Coil.load(activity, image) { + target { + val canvas = Canvas(bmp) + it.setBounds(0, 0, bmp.width, bmp.height) + it.draw(canvas) } - AsyncTask.execute { app.db.feedbackMessageDao().add(feedbackMessage) } - var message = Message.Builder() - .setUser(user!!) - .setRight(true) - .setText(feedbackMessage.text) - .hideIcon(true) - .build() - mChatView!!.send(message) - mChatView!!.inputText = "" - b.textInput.setText("") - if (firstSend) { - Anim.fadeOut(b.inputLayout, 500, object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) { - - } - - override fun onAnimationEnd(animation: Animation) { - b.inputLayout.visibility = View.GONE - Anim.fadeIn(b.chatLayout, 500, null) - } - - override fun onAnimationRepeat(animation: Animation) { - - } - }) - if (deviceToSend == null) { - // we are not the developer - val feedbackMessage2 = FeedbackMessage(true, "Postaram się jak najszybciej Tobie odpowiedzieć. Dostaniesz powiadomienie o odpowiedzi, która pokaże się w tym miejscu.") - AsyncTask.execute { app.db.feedbackMessageDao().add(feedbackMessage2) } - message = Message.Builder() - .setUser(dev!!) - .setRight(false) - .setText(feedbackMessage2.text) - .hideIcon(false) - .build() - mChatView!!.receive(message) - } - firstSend = false - } - } else { - Toast.makeText(app, "Nie udało się wysłać wiadomości.", Toast.LENGTH_SHORT).show() } } + } + } + + override fun getId() = id.toString() + override fun getName() = userName + override fun setIcon(bmp: Bitmap) {} + } + + private fun send(text: String?) { + if (text?.isEmpty() != false) { + Toast.makeText(activity, "Podaj treść wiadomości.", Toast.LENGTH_SHORT).show() + return + } + + if (isDev && currentDeviceId == null || currentDeviceId == "szkolny.eu") { + Toast.makeText(activity, "Wybierz urządzenie docelowe.", Toast.LENGTH_SHORT).show() + return + } + + launch { + val message = withContext(Dispatchers.Default) { + try { + api.sendFeedbackMessage( + senderName = App.profile.accountName ?: App.profile.studentNameLong, + targetDeviceId = if (isDev) currentDeviceId else null, + text = text + )?.also { + app.db.feedbackMessageDao().add(it) + } + } catch (ignore: Exception) { null } + } + + if (message == null) { + Toast.makeText(app, "Nie udało się wysłać wiadomości.", Toast.LENGTH_SHORT).show() + return@launch + } + + b.chatLayout.visibility = View.VISIBLE + b.inputLayout.visibility = View.GONE + + b.textInput.text = null + b.chatView.inputText = "" + + val chatMessage = getChatMessage(message) + if (message.received) chatView.receive(chatMessage) + else chatView.send(chatMessage) + } } private fun openFaq() { openUrl(activity, "http://szkolny.eu/pomoc/") - MaterialDialog.Builder(activity) - .title(R.string.faq_back_title) - .content(R.string.faq_back_text) - .positiveText(R.string.yes) - .negativeText(R.string.no) - .onPositive { dialog, which -> - - } - .onNegative { dialog, which -> - - } - .show() } override fun onResume() { super.onResume() - if (receiver != null) - activity.registerReceiver(receiver, IntentFilter("pl.szczodrzynski.edziennik.ui.modules.base.FeedbackActivity")) + EventBus.getDefault().register(this) } override fun onPause() { super.onPause() - if (receiver != null) - activity.unregisterReceiver(receiver) + EventBus.getDefault().unregister(this) } }