diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java index 3f0ca2c..0c5a721 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java @@ -1,5 +1,7 @@ package com.example.tampopo_client.viewmodels; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import java.util.ArrayList; @@ -8,17 +10,35 @@ public class ChatViewModel extends RealTimeViewModel { private final List notificationListeners = new ArrayList<>(); - private static final double NOTIFICATION_RECEIVE_PROBABILITY = 0.01; + //private static final double NOTIFICATION_RECEIVE_PROBABILITY = 0.5; + //natty + private boolean notificationSent = false; // 一度だけ送信するためのフラグ + + +// @Override +// public Runnable onUpdate() { +// return () -> { +// // 1% の確率で onNotificationReceived() が呼び出される +// double borderValue = Math.floor(Math.random() * 100); +// double currentValue = NOTIFICATION_RECEIVE_PROBABILITY * 100; +// if (currentValue >= borderValue) { +// Log.d("ChatViewModel", "Received test notification."); +// notificationListeners.forEach(listener -> listener.onNotificationReceived()); +// } +// }; +// } + //natty @Override public Runnable onUpdate() { return () -> { - // 1% の確率で onNotificationReceived() が呼び出される - double borderValue = Math.floor(Math.random() * 100); - double currentValue = NOTIFICATION_RECEIVE_PROBABILITY * 100; - if (currentValue >= borderValue) { - Log.d("ChatViewModel", "Received test notification."); - notificationListeners.forEach(listener -> listener.onNotificationReceived()); + if (!notificationSent) { + // 10秒後に一回だけ通知を送る + notificationSent = true; // もう送らない + new Handler(Looper.getMainLooper()).postDelayed(() -> { + Log.d("ChatViewModel", "Received test notification (after 10s)."); + notificationListeners.forEach(NotificationListener::onNotificationReceived); + }, 10_000); // 10秒(10000ms) } }; } diff --git a/app/src/main/java/com/example/tampopo_client/views/MainActivity.java b/app/src/main/java/com/example/tampopo_client/views/MainActivity.java index 458c26e..e851900 100644 --- a/app/src/main/java/com/example/tampopo_client/views/MainActivity.java +++ b/app/src/main/java/com/example/tampopo_client/views/MainActivity.java @@ -5,8 +5,10 @@ import android.app.NotificationManager; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.ArrayAdapter; @@ -20,9 +22,12 @@ import android.widget.Toast; import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -31,6 +36,7 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; +import android.Manifest; import com.example.tampopo_client.R; import com.example.tampopo_client.Tampopo; import com.example.tampopo_client.models.Activity; @@ -62,6 +68,7 @@ private ChatViewModel chatViewModel; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -73,6 +80,21 @@ return insets; }); + //疑似的な通知のためにchatViewModelを定義する + chatViewModel = new ViewModelProvider(this).get(ChatViewModel.class); + chatViewModel.addNotificationListener(this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions( + this, + new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, + 1001 + ); + } + } + +// handleIncomingIntent(getIntent()); //メイン画面からフレンド一覧画面への遷移 ImageButton friendButton = (ImageButton) findViewById(R.id.friend); friendButton.setOnClickListener(new View.OnClickListener() { @@ -343,18 +365,11 @@ //プッシュ通知 private void showChatNotification(String friendName) { - NotificationManager notificationManager = - (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// NotificationManager notificationManager = +// (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); String channelId = "chat_channel"; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - channelId, - "Chat Notifications", - NotificationManager.IMPORTANCE_HIGH - ); - notificationManager.createNotificationChannel(channel); - } NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) .setSmallIcon(R.drawable.default_icon) // 通知アイコン @@ -362,13 +377,106 @@ .setContentText(friendName + " さんから通話があります") .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true); // タップしたら消える + NotificationChannel channel = new NotificationChannel( + channelId, + "Chat Notifications", + NotificationManager.IMPORTANCE_HIGH + ); + notificationManager.createNotificationChannel(channel); - //notificationManager.notify(1, builder.build()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission( + this, + android.Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED) { + Log.d("NotificationTest", "notify() 呼ばれた!!"); + notificationManager.notify(0, builder.build()); + } else { + // 許可されていない → リクエストする + ActivityCompat.requestPermissions( + this, + new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, + 1001 + ); + } +// ShapeableImageView iconView = new ShapeableImageView(this); +// iconView.setStrokeColor(ContextCompat.getColorStateList(this, R.color.black)); + + } else { + Log.d("NotificationTest", "notify() 呼ばれた!!"); + notificationManager.notify(0, builder.build()); + } + } +// private static final int REQUEST_CODE_POST_NOTIFICATIONS = 1001; +// +// // 通知権限をチェックしてリクエストする +// private void checkAndRequestNotificationPermission() { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// // Android 13以上 → POST_NOTIFICATIONS の確認 +// if (ContextCompat.checkSelfPermission( +// this, +// android.Manifest.permission.POST_NOTIFICATIONS +// ) != PackageManager.PERMISSION_GRANTED) { +// +// ActivityCompat.requestPermissions( +// this, +// new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, +// REQUEST_CODE_POST_NOTIFICATIONS +// ); +// } +// } else { +// // Android 12以下 → 権限は存在しないので説明だけ +// Toast.makeText(this, +// "通知を受け取るには端末の設定から通知を有効にしてください", +// Toast.LENGTH_LONG).show(); +// } +// } +// @Override +// public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { +// super.onRequestPermissionsResult(requestCode, permissions, grantResults); +// +// if (requestCode == REQUEST_CODE_POST_NOTIFICATIONS) { +// if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { +// Toast.makeText(this, "通知が許可されました", Toast.LENGTH_SHORT).show(); +// } else { +// Toast.makeText(this, "通知が拒否されました。設定から有効にできます", Toast.LENGTH_LONG).show(); +// } +// } +// } + private void highlightUserIcon(String userId) { + runOnUiThread(() -> { + LinearLayout container = userViews.get(userId); + if (container != null && container.getChildCount() > 0) { + View v = container.getChildAt(0); + if (v instanceof ShapeableImageView) { + ShapeableImageView icon = (ShapeableImageView) v; + icon.setStrokeColor(ContextCompat.getColorStateList(this, R.color.red)); + icon.setStrokeWidth(6f); // 太めにして目立たせる + } + } + }); + } + + private void clearUserIconHighlight(String userId) { + runOnUiThread(() -> { + LinearLayout container = userViews.get(userId); + if (container != null && container.getChildCount() > 0) { + View v = container.getChildAt(0); + if (v instanceof ShapeableImageView) { + ShapeableImageView icon = (ShapeableImageView) v; + icon.setStrokeWidth(0f); // 初期状態(枠なし)に戻す + } + } + }); + } + + //通話が来たときのダイアログ // 通話リクエストを受け取った時に呼び出すダイアログ public void showCallRequestDialog(Context context, String fromUserName) { + Dialog dialog = new Dialog(context); dialog.setContentView(R.layout.dialog_chat_receved); dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); @@ -395,6 +503,26 @@ @Override public void onNotificationReceived() { - // TODO: 通知を受信したときの処理を実装する + // 通知を受信したときにダイアログを表示 + runOnUiThread(() -> showChatNotification("user01")); + // アイコンを赤枠に + runOnUiThread(() -> highlightUserIcon("user01")); } +// @Override +// protected void onNewIntent(Intent intent) { +// super.onNewIntent(intent); +// setIntent(intent); // 忘れずに更新 +// handleIncomingIntent(intent); +// } + +// private void handleIncomingIntent(Intent intent) { +// if (intent != null && intent.hasExtra("incoming_user_id")) { +// String incomingUserId = intent.getStringExtra("incoming_user_id"); +// if (incomingUserId != null) { +// // userIdからユーザ名を取得する処理を入れてもいいし +// showCallRequestDialog(this, incomingUserId); +// } +// } +// } + } \ No newline at end of file