[Feedback] Add new feedback fragment and API.

This commit is contained in:
Kuba Szczodrzyński 2020-01-26 20:05:32 +01:00
parent 759afcf3ca
commit 21b2e5d194
16 changed files with 368 additions and 668 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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<List<Update>>? {
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
}
}

View File

@ -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<ApiResponse<List<Update>>>
@POST("feedbackMessage")
fun feedbackMessage(@Body request: FeedbackMessageRequest): Call<ApiResponse<FeedbackMessageResponse>>
}

View File

@ -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
)

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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<FeedbackMessage> feedbackMessageList);
@Query("DELETE FROM feedbackMessages")
void clear();
@Query("SELECT * FROM feedbackMessages")
List<FeedbackMessage> getAllNow();
@Query("SELECT * FROM feedbackMessages WHERE fromUser = :fromUser")
List<FeedbackMessage> getAllByUserNow(String fromUser);
@Query("SELECT * FROM feedbackMessages")
LiveData<List<FeedbackMessage>> getAll();
@Query("SELECT *, COUNT(*) AS messageCount FROM feedbackMessages GROUP BY fromUser ORDER BY sentTime DESC")
List<FeedbackMessageWithCount> getAllWithCountNow();
}

View File

@ -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<FeedbackMessage>)
@Query("DELETE FROM feedbackMessages")
fun clear()
@get:Query("SELECT * FROM feedbackMessages ORDER BY sentTime DESC LIMIT 50")
val allNow: List<FeedbackMessage>
@Query("SELECT * FROM feedbackMessages WHERE deviceId = :deviceId ORDER BY sentTime DESC LIMIT 50")
fun getByDeviceIdNow(deviceId: String): List<FeedbackMessage>
@get:Query("SELECT * FROM feedbackMessages")
val all: LiveData<List<FeedbackMessage>>
@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<FeedbackMessage.WithCount>
}

View File

@ -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() {
}
}

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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;")
}
}

View File

@ -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<Profile>, 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<Profile>, val message:
app.db.notificationDao().addAll(notificationList)
PostNotifications(app, notificationList)
}
}
}

View File

@ -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<FeedbackMessageWithCount> 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<FeedbackMessage> 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<FeedbackMessage> 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);
}
}

View File

@ -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<FeedbackMessage>? = 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)
}
}