commit 95da08f1397d3d376f870bae9de072d82526f869 Author: Mysaa Date: Fri Aug 27 02:22:00 2021 +0200 Premier commit, création de l'application avec connection au terminal maître. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..352cf03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +gradlew +gradlew.bat +gradle/ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..60bb18a --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "com.bernard.murderator" + minSdk 21 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.navigation:navigation-fragment:2.3.5' + implementation 'androidx.navigation:navigation-ui:2.3.5' + implementation files('/home/mysaa/Documents/ProjetsGit/mysaa/Murderator.git/build/libs/MurderatorAPI.jar') + implementation files('/home/mysaa/Documents/ProjetsGit/bernard/BernardLibs.git/build/libs/BernardLibs.jar') + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0f7daec --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..55c35b1 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/bernard/murderator/JoueurFragmentAdapter.java b/app/src/main/java/com/bernard/murderator/JoueurFragmentAdapter.java new file mode 100644 index 0000000..50a5559 --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/JoueurFragmentAdapter.java @@ -0,0 +1,54 @@ +package com.bernard.murderator; + +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.bernard.murderator.activities.ActionsJoueurFragment; +import com.bernard.murderator.activities.ChatJoueurFragment; +import com.bernard.murderator.activities.InventaireJoueurFragment; +import com.bernard.murderator.activities.JoueurActivity; + +public class JoueurFragmentAdapter extends FragmentStateAdapter { + + public static final String PERSO_NAME_VAR = "com.bernard.murderator.activities.JoueurFragmentAdapter.persoName"; + + String playerName; + + InventaireJoueurFragment invFrag; + ActionsJoueurFragment actFrag; + ChatJoueurFragment chatFrag; + + public JoueurFragmentAdapter(@NonNull JoueurActivity fragmentActivity, String playerName) { + super(fragmentActivity); + this.playerName = playerName; + } + + @NonNull + @Override + public Fragment createFragment(int position) { + switch (position){ + case 0: + if(invFrag==null) + invFrag = new InventaireJoueurFragment(playerName); + return invFrag; + case 1: + if(actFrag==null) + actFrag = new ActionsJoueurFragment(playerName); + return actFrag; + case 2: + if(chatFrag==null) + chatFrag = new ChatJoueurFragment(); + return chatFrag; + } + throw new IllegalArgumentException("Cet adapteur ne fournis pas que 3 fragments, il n'y en a pas de "+position+"ème ..."); + } + + @Override + public int getItemCount() { + return 3; + } +} diff --git a/app/src/main/java/com/bernard/murderator/MurderApplication.java b/app/src/main/java/com/bernard/murderator/MurderApplication.java new file mode 100644 index 0000000..a558e06 --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/MurderApplication.java @@ -0,0 +1,191 @@ +package com.bernard.murderator; + +import android.app.Activity; +import android.app.Application; +import android.util.Log; + +import com.bernard.murder.model.Action; +import com.bernard.murder.model.Objet; +import com.bernard.murder.model.messages.Message; +import com.bernard.murder.model.messages.Thread; +import com.bernard.murderator.connection.PhoneServer; + +import java.net.SocketAddress; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class MurderApplication extends Application { + + private PhoneServer serveur; + + public Activity mainActivity; + + + final Map> playerActions = new HashMap<>(); // + final Map playerActionsLastUpdate = new HashMap<>(); + final Map> playerActionsUpdateListeners = new HashMap<>(); + + final Map> inventaireMap = new HashMap<>(); // + final Map inventaireMapLastUpdate = new HashMap<>(); // + final Map> inventaireUpdateListeners = new HashMap<>(); + + final Map> messages = new HashMap<>(); // + + Set joueurs = null; // + private final Object joueursTriggerer = new Object(); + + public boolean initServeur(SocketAddress address){ + if(serveur==null) { + PhoneServer serv = new PhoneServer(address, this); + boolean res = serv.checkMaster(); + if (res) + serveur = serv; + else + serv.close(); + return res; + }else + return serveur.checkMaster(); + } + + public PhoneServer getServeur(){ + if(serveur!=null) + return serveur; + throw new IllegalStateException("Le serveur de l'application n'a pas été initialisé."); + } + + public boolean initToken(boolean isOp, String name, String mdp){ + serveur.queryToken(name, mdp, isOp); + + while(!serveur.hasToken()){ + try { + synchronized (serveur) { + serveur.wait(dataWaitTimeout); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return true; + } + + private static final long dataWaitTimeout = 10_000; + + public Set getPersosNames() { + if(joueurs==null) { + serveur.queryPlayerList(); + while(joueurs==null){ + try { + synchronized (joueursTriggerer) { + joueursTriggerer.wait(dataWaitTimeout); + } + } catch (InterruptedException e) { + Log.e("MurderApplication","L'attente de la liste des joueurs a été interrompue.",e); + } + } + } + return joueurs; + } + + public Set getActions(String playerName) { + long askTime = System.currentTimeMillis(); + if(!(playerActionsLastUpdate.containsKey(playerName) && playerActionsLastUpdate.get(playerName)==null)) { + serveur.queryActions(playerName,false); + while(!playerActionsLastUpdate.containsKey(playerName) || + (playerActionsLastUpdate.get(playerName)!=null && + playerActionsLastUpdate.get(playerName) getInventaire(String invName) { + long askTime = System.currentTimeMillis(); + if(!(inventaireMapLastUpdate.containsKey(invName) && inventaireMapLastUpdate.get(invName)==null)) { + serveur.queryInventory(invName,false); + while(inventaireMapLastUpdate.containsKey(invName) && + !(inventaireMapLastUpdate.get(invName)!=null && + inventaireMapLastUpdate.get(invName) actions){ + playerActions.put(player,actions); + if(!(playerActionsLastUpdate.containsKey(player) && playerActionsLastUpdate.get(player)==null)) + playerActionsLastUpdate.put(player,System.currentTimeMillis()); + synchronized (playerActionsLastUpdate) { + playerActionsLastUpdate.notifyAll(); + } + if(playerActionsUpdateListeners.containsKey(player)) + for(Runnable r: playerActionsUpdateListeners.get(player)) + r.run(); + } + + public void updateInventaire(String invName, Set inventaire){ + inventaireMap.put(invName,inventaire); + if(!(inventaireMapLastUpdate.containsKey(invName) && inventaireMapLastUpdate.get(invName)==null)) + inventaireMapLastUpdate.put(invName,System.currentTimeMillis()); + synchronized (inventaireMapLastUpdate) { + inventaireMapLastUpdate.notifyAll(); + } + if(inventaireUpdateListeners.containsKey(invName)) + for(Runnable r: inventaireUpdateListeners.get(invName)) + r.run(); + } + + public void updateJoueurs(Set joueurs){ + if(this.joueurs==null) + this.joueurs = new HashSet<>(); + this.joueurs.clear(); + this.joueurs.addAll(joueurs); + synchronized (joueursTriggerer) { + joueursTriggerer.notifyAll(); + } + } + + + public void addPlayerActionsUpdateListener(String player,Runnable onUpdate){ + if(!playerActionsLastUpdate.containsKey(player) || playerActionsLastUpdate.get(player)!=null) + playerActionsLastUpdate.put(player,null); + if(!playerActionsUpdateListeners.containsKey(player)) + playerActionsUpdateListeners.put(player,new HashSet<>()); + playerActionsUpdateListeners.get(player).add(onUpdate); + } + public void addInventoryUpdateListener(String invName,Runnable onUpdate){ + if(!inventaireMapLastUpdate.containsKey(invName) || inventaireMapLastUpdate.get(invName)!=null) + inventaireMapLastUpdate.put(invName,null); + if(!inventaireUpdateListeners.containsKey(invName)) + inventaireUpdateListeners.put(invName,new HashSet<>()); + inventaireUpdateListeners.get(invName).add(onUpdate); + } + + public void receivedMessage(Message message, long threadId, int threadPos){ + + } + + public void openedThread(Thread tt){ + + } + + public void closedThread(long closedUid, long closeTimestamp) { + + } +} diff --git a/app/src/main/java/com/bernard/murderator/activities/ActionsJoueurFragment.java b/app/src/main/java/com/bernard/murderator/activities/ActionsJoueurFragment.java new file mode 100644 index 0000000..0430903 --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/ActionsJoueurFragment.java @@ -0,0 +1,155 @@ +package com.bernard.murderator.activities; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.bernard.murder.model.Action; +import com.bernard.murderator.JoueurFragmentAdapter; +import com.bernard.murderator.MurderApplication; +import com.bernard.murderator.R; +import com.bernard.util.ParseUtils; + +import java.sql.Date; +import java.text.DateFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +public class ActionsJoueurFragment extends Fragment { + + ListView actionListView; + + String persoName; + + // Si android re-crée le fragment + public ActionsJoueurFragment(){} + + public ActionsJoueurFragment(String persoName){ + this.persoName = persoName; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View fragView = inflater.inflate(R.layout.fragment_actions_joueur,container,false); + actionListView = fragView.findViewById(R.id.actions_list); + + if(getContext()!=null) { + MurderApplication theApp = ((MurderApplication) getContext().getApplicationContext()); + ActionAdapter ladapter = new ActionAdapter(getContext(), persoName); + actionListView.setAdapter(ladapter); + }else { + Log.e("ActionsJoueurFragment", "L'application n'a pas été créée !"); + } + + return fragView; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putString(JoueurFragmentAdapter.PERSO_NAME_VAR, persoName); + super.onSaveInstanceState(outState); + } + + @Override + public void onViewStateRestored(@Nullable Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if(persoName ==null){ + // Alors on le récupères du bundle + persoName = savedInstanceState.getString(JoueurFragmentAdapter.PERSO_NAME_VAR); + } + } + + public class ActionAdapter extends ArrayAdapter{ + + Timer updateTimer; + + public ActionAdapter(@NonNull Context context, String persoName) { + super(context, 0); + Log.d("ActionAdapter","Création de l'adapteur"); + MurderApplication theApp = ((MurderApplication) context.getApplicationContext()); + Set actions = theApp.getActions(persoName); + updateElements(actions); + Log.d("ActionAdapter","Les éléments ont été mises à jour"); + + updateTimer = new Timer("Action-updater"); + updateTimer.schedule(new TimerTask() { + @Override + public void run() { + if(getActivity()!=null) + getActivity().runOnUiThread(ActionAdapter.this::updateTexts); + } + }, 100, 1000); + Log.d("ActionAdapter","le timer est lancé"); + } + + public void updateElements(Set acts){ + this.clear(); + List actz = new ArrayList<>(acts); + Collections.sort(actz,(a,b) -> a.getName().compareTo(b.getName())); + this.addAll(actz); + this.notifyDataSetChanged(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + Action act = getItem(position); + + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(R.layout.action_list_item_layout, parent, false); + } + + TextView actionNameText = convertView.findViewById(R.id.action_name_text); + TextView actionTimeText = convertView.findViewById(R.id.action_time_text); + + toUpdate.put(position,actionTimeText); + + actionNameText.setText(act.getName()); + updateTexts(); + + return convertView; + } + Map toUpdate = new HashMap<>(); + public void updateTexts(){ + for (int i : toUpdate.keySet()) { + Action act = getItem(i); + String newText; + if(act.canBeLaunched()) + newText = getContext().getString(R.string.action_available_text); + else + newText = String.format( + getContext().getString(R.string.action_waiting_text), + ParseUtils.dumpTimeLength(act.timeToWaitLeft()), + DateFormat.getTimeInstance(DateFormat.MEDIUM).format(new Date(act.dateReset()))); + + toUpdate.get(i).setText(newText); + + } + } + } + +} diff --git a/app/src/main/java/com/bernard/murderator/activities/ChatJoueurFragment.java b/app/src/main/java/com/bernard/murderator/activities/ChatJoueurFragment.java new file mode 100644 index 0000000..0d6a813 --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/ChatJoueurFragment.java @@ -0,0 +1,23 @@ +package com.bernard.murderator.activities; + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.bernard.murderator.R; + +public class ChatJoueurFragment extends Fragment { + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_chat_joueur,container,false); + } + +} diff --git a/app/src/main/java/com/bernard/murderator/activities/ConnectionJoueurSelectionActivity.java b/app/src/main/java/com/bernard/murderator/activities/ConnectionJoueurSelectionActivity.java new file mode 100644 index 0000000..7f00587 --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/ConnectionJoueurSelectionActivity.java @@ -0,0 +1,81 @@ +package com.bernard.murderator.activities; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import com.bernard.murder.model.Personnage; +import com.bernard.murderator.MurderApplication; +import com.google.android.material.snackbar.Snackbar; + +import androidx.appcompat.app.AppCompatActivity; + +import android.view.View; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Toast; + + +import com.bernard.murderator.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class ConnectionJoueurSelectionActivity extends AppCompatActivity { + + Activity thisActivity; + MurderApplication thisApplication; + + ListView liste; + EditText mdp; + + EditText masterIP; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + thisActivity = this; + thisApplication = (MurderApplication) getApplicationContext(); + setContentView(R.layout.activity_connection_joueur_selection); + + liste = findViewById(R.id.player_select_list); + mdp = findViewById(R.id.player_mdp); + + + + List joueurs = new ArrayList(thisApplication.getPersosNames()); + Collections.sort(joueurs); + + liste.setAdapter( + new ArrayAdapter(this, android.R.layout.simple_list_item_1,joueurs) + ); + liste.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); + liste.setOnItemClickListener((parent,view,position,id)->{ + String playerName = (String) liste.getItemAtPosition(position); + if(playerName==null){ + Toast.makeText(thisActivity,R.string.no_player_selected,Toast.LENGTH_SHORT).show(); + return; + } + if(mdp.getText().toString().isEmpty()){ + Toast.makeText(thisActivity,R.string.no_password_given,Toast.LENGTH_SHORT).show(); + return; + } + if(thisApplication.initToken(false,playerName,mdp.getText().toString())){ + Intent intent = new Intent(thisActivity,JoueurActivity.class); + intent.putExtra(JoueurActivity.JOUEUR_NAME_INTENT,playerName); + + startActivity(intent); + }else{ + Toast.makeText(thisActivity,R.string.connection_failed,Toast.LENGTH_SHORT).show(); + } + }); + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/bernard/murderator/activities/ConnectionOpActivity.java b/app/src/main/java/com/bernard/murderator/activities/ConnectionOpActivity.java new file mode 100644 index 0000000..d46778a --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/ConnectionOpActivity.java @@ -0,0 +1,17 @@ +package com.bernard.murderator.activities; + +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import com.bernard.murderator.R; + +public class ConnectionOpActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState) ; + setContentView(R.layout.activity_main); + } + +} diff --git a/app/src/main/java/com/bernard/murderator/activities/ConnectionSelectionActivity.java b/app/src/main/java/com/bernard/murderator/activities/ConnectionSelectionActivity.java new file mode 100644 index 0000000..1324f9c --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/ConnectionSelectionActivity.java @@ -0,0 +1,64 @@ +package com.bernard.murderator.activities; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.bernard.murderator.MurderApplication; +import com.bernard.murderator.R; +import com.bernard.murderator.connection.PhoneServer; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class ConnectionSelectionActivity extends AppCompatActivity { + + Activity thisActivity; + MurderApplication thisApplication; + + Button joueurButton, operateurButton, qrCodeButton, micButton, enceinteButton; + EditText masterIP; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + thisActivity = this; + thisApplication = (MurderApplication) getApplicationContext(); + setContentView(R.layout.activity_connection_selection); + + joueurButton = findViewById(R.id.connect_as_player); + operateurButton = findViewById(R.id.connect_as_op); + micButton = findViewById(R.id.connect_as_mic); + enceinteButton = findViewById(R.id.connect_as_speaker); + qrCodeButton = findViewById(R.id.connect_with_qrcode); + + masterIP = findViewById(R.id.editTextMasterIP); + + + + joueurButton.setOnClickListener((v) -> { + //TODO fetch player list from remote + if(initServer(masterIP.getText().toString())) { + Intent intent = new Intent(thisActivity, ConnectionJoueurSelectionActivity.class); + startActivity(intent); + } + }); + } + + public boolean initServer(String address){ + SocketAddress addr = new InetSocketAddress(address, PhoneServer.communicationPort); + boolean result = thisApplication.initServeur(addr); + + if(!result) + Toast.makeText(this,R.string.master_not_found,Toast.LENGTH_SHORT).show(); + return result; + } + + //TODO Peut recevoir en intent une URL/qrcode correspondant à une URL contenant le type de connection, le mot de passe et le pseudo/nomDuJoueur + +} diff --git a/app/src/main/java/com/bernard/murderator/activities/InventaireJoueurFragment.java b/app/src/main/java/com/bernard/murderator/activities/InventaireJoueurFragment.java new file mode 100644 index 0000000..1b26eec --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/InventaireJoueurFragment.java @@ -0,0 +1,30 @@ +package com.bernard.murderator.activities; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.bernard.murderator.R; + +public class InventaireJoueurFragment extends Fragment { + + String invName; + + public InventaireJoueurFragment(){} + + public InventaireJoueurFragment(String invName){ + this.invName = invName; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_inventaire_joueur,container,false); + } + +} diff --git a/app/src/main/java/com/bernard/murderator/activities/JoueurActivity.java b/app/src/main/java/com/bernard/murderator/activities/JoueurActivity.java new file mode 100644 index 0000000..7eff00c --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/activities/JoueurActivity.java @@ -0,0 +1,42 @@ +package com.bernard.murderator.activities; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.widget.ViewPager2; + +import com.bernard.murderator.JoueurFragmentAdapter; +import com.bernard.murderator.MurderApplication; +import com.bernard.murderator.R; + +public class JoueurActivity extends FragmentActivity { + + public static final String JOUEUR_NAME_INTENT = "com.bernard.murderator.JoueurActivity.joueurName"; + + String playerName; + + + ViewPager2 pager; + + JoueurFragmentAdapter adapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_joueur); + + Intent intent = getIntent(); + playerName = intent.getStringExtra(JOUEUR_NAME_INTENT); + + ((MurderApplication)getApplication()).mainActivity = this; + pager = findViewById(R.id.view_pager); + + adapter = new JoueurFragmentAdapter(this, playerName); + pager.setAdapter(adapter); + } + + public String getPlayerName() { + return playerName; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bernard/murderator/connection/PhoneServer.java b/app/src/main/java/com/bernard/murderator/connection/PhoneServer.java new file mode 100644 index 0000000..d71f8b0 --- /dev/null +++ b/app/src/main/java/com/bernard/murderator/connection/PhoneServer.java @@ -0,0 +1,265 @@ +package com.bernard.murderator.connection; + +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import com.bernard.murder.audio.Codes; +import com.bernard.murder.audio.Serveur; +import com.bernard.murder.model.Action; +import com.bernard.murder.model.Objet; +import com.bernard.murder.model.messages.Message; +import com.bernard.murder.model.messages.Thread; +import com.bernard.murderator.MurderApplication; +import com.bernard.murderator.R; +import com.bernard.util.BytesUtils; + +import java.io.IOException; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class PhoneServer { + + public static int communicationPort = 35295; + + Serveur serv; + String token; + + SocketAddress masterAddress; + MurderApplication app; + + Set answeredUUIDs = new HashSet<>(); + + + public PhoneServer(SocketAddress address, MurderApplication app){ + this.masterAddress = address; + this.app = app; + + try { + initServer(); + } catch (SocketException | UnknownHostException e) { + Log.e("PhoneServer","Impossible d'initialiser le serveur",e); + } + } + + public void initServer() throws SocketException, UnknownHostException { + if(serv==null) { + serv = new Serveur(this::receiveCommand, PhoneServer.communicationPort); + serv.setNetworkOnSeparatedThread(true); + } + } + + public void receiveCommand(ByteBuffer data, SocketAddress senderAddress) { + byte commande = data.get(); + Log.d("PhoneServer","Commande reçue : " + commande + " de " + senderAddress + " de taille " + (data.limit() + 1)); + + switch (commande){ + case Codes.ACCES_PARTIE: + receivePartieCommand(data, senderAddress); + break; + case Codes.PONG: + synchronized (masterCheckMonitored) { + masterResponce = true; + masterCheckMonitored.notifyAll(); + } + break; + default: + Log.w("PhoneServer","Je ne sais pas recevoir les commandes "+commande); + } + } + + public void receivePartieCommand(ByteBuffer data, SocketAddress senderAddress) { + byte commandePartie = data.get(); + Log.d("PhoneServer","Partie-commande recue: "+commandePartie); + switch(commandePartie){ + case Codes.Partie.ACTIONS_STATUS: + Log.d("PhoneServer", Arrays.toString(data.array())); + String persoName = BytesUtils.readString(data); + int actCount = data.getInt(); + Set actions = new HashSet<>(); + Log.d("PhoneServer","J'ai reçu "+actCount+" actions"); + for (int i = 0; i < actCount; i++) { + Log.d("PhoneServer","Le numéro "+i+" ..."); + String actName = BytesUtils.readString(data); + long basetime = data.getLong(); + long triggertime = data.getLong(); + Action act = new Action(actName,basetime,triggertime); + actions.add(act); + } + app.updatePlayerActions(persoName,actions); + break; + case Codes.Partie.INVENTORY_CONTENT: + String invName = BytesUtils.readString(data); + int invCount = data.getInt(); + Set objets = new HashSet<>(); + for (int i = 0; i < invCount; i++) { + String objName = BytesUtils.readString(data); + Objet obj = new Objet(objName); + objets.add(obj); + } + + app.updateInventaire(invName,objets); + break; + case Codes.Partie.GIVE_PLAYER_LIST: + int nbrJoueurs = data.getInt(); + Set joueurs = new HashSet<>(); + for (int i = 0; i < nbrJoueurs; i++) { + String joueur = BytesUtils.readString(data); + joueurs.add(joueur); + } + + app.updateJoueurs(joueurs); + break; + + case Codes.Partie.GIVING_TOKEN: + String newToken = BytesUtils.readString(data); + Log.d("PhoneServer","On a reçu un token ! "+newToken); + if(!newToken.isEmpty()) + this.token = newToken; + else + authError(token); + synchronized (this) { + this.notifyAll(); + } + break; + + case Codes.Partie.AUTH_ERROR: + String erroredToken = BytesUtils.readString(data); + authError(erroredToken); + break; + + case Codes.Partie.CREATED_NEW_THREAD: + UUID requestId = new UUID(data.getLong(),data.getLong()); + String persoNameT = BytesUtils.readString(data); + long threadUid = data.getLong(); + long startTimestamp = data.getLong(); + + Thread tt = new Thread(persoNameT,startTimestamp,threadUid); + + app.openedThread(tt); + + answeredUUIDs.add(requestId); + break; + case Codes.Partie.CLOSED_THREAD: + long closedUid = data.getLong(); + long closeTimestamp = data.getLong(); + + app.closedThread(closedUid,closeTimestamp); + break; + case Codes.Partie.NEW_MESSAGE: + long sentTimestamp = data.getLong(); + long threadId = data.getLong(); + int threadPosition = data.getInt(); + String emmeteur = BytesUtils.readString(data); + String msgText = BytesUtils.readString(data); + + Message msg = new Message(emmeteur,msgText,sentTimestamp); + + app.receivedMessage(msg,threadId,threadPosition); + + break; + default: + Log.w("PhoneServer","Je ne sais pas recevoir les sous-commandes "+commandePartie); + } + } + + public void sendMessage(String text, String emmeteur, long threadUid){ + + } + + public void queryPlayerList(){ + ByteBuffer bb = ByteBuffer.allocate(Codes.packetMaxSize); + bb.put(Codes.ACCES_PARTIE); + bb.put(Codes.Partie.ASK_PLAYER_LIST); + + try { + serv.sendData(bb,masterAddress); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void queryInventory(String invName, boolean watch){ + ByteBuffer bb = ByteBuffer.allocate(Codes.packetMaxSize); + bb.put(Codes.ACCES_PARTIE); + bb.put(watch?Codes.Partie.ASK_INVENTORY_WATCH:Codes.Partie.ASK_INVENTORY); + BytesUtils.writeString(bb,token); + BytesUtils.writeString(bb,invName); + + try { + serv.sendData(bb,masterAddress); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void queryActions(String persoName, boolean watch){ + ByteBuffer bb = ByteBuffer.allocate(Codes.packetMaxSize); + bb.put(Codes.ACCES_PARTIE); + bb.put(watch?Codes.Partie.ASK_ACTIONS_WATCH:Codes.Partie.ASK_ACTIONS); + BytesUtils.writeString(bb,token); + BytesUtils.writeString(bb,persoName); + + try { + serv.sendData(bb,masterAddress); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void queryToken(String playerName, String mdp, boolean isOp){ + ByteBuffer bb = ByteBuffer.allocate(Codes.packetMaxSize); + bb.put(Codes.ACCES_PARTIE); + bb.put(isOp?Codes.Partie.ASK_OP_TOKEN:Codes.Partie.ASK_JOUEUR_TOKEN); + BytesUtils.writeString(bb,playerName); + BytesUtils.writeString(bb,mdp); + + try { + serv.sendData(bb,masterAddress); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void authError(String token){ + new java.lang.Thread(() -> { + Looper.prepare(); + if(app.mainActivity!=null && (app.mainActivity.isFinishing() || app.mainActivity.isDestroyed())) + app.mainActivity.finish(); + Toast.makeText(app, app.getResources().getString(R.string.auth_error,token),Toast.LENGTH_SHORT).show(); + }).start(); + } + + + + public static final long masterCheckTimeout = 10_000; + private final Lock masterCheckLock = new ReentrantLock(); + public final Object masterCheckMonitored = new Object(); + private boolean masterResponce = false; + public synchronized boolean checkMaster(){ + synchronized (masterCheckMonitored) { + masterResponce = false; + try { + serv.sendData(new byte[]{Codes.PING}, masterAddress); + masterCheckMonitored.wait(masterCheckTimeout); + } catch (InterruptedException | IOException ignored) {} + } + return masterResponce; + } + + public boolean hasToken(){ + return token==null; + } + + public void close() { + serv.close(); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..da1dcd9 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..ca3826a --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/action_list_item_layout.xml b/app/src/main/res/layout/action_list_item_layout.xml new file mode 100644 index 0000000..621f38b --- /dev/null +++ b/app/src/main/res/layout/action_list_item_layout.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_connection_joueur_selection.xml b/app/src/main/res/layout/activity_connection_joueur_selection.xml new file mode 100644 index 0000000..7d74d00 --- /dev/null +++ b/app/src/main/res/layout/activity_connection_joueur_selection.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_connection_op.xml b/app/src/main/res/layout/activity_connection_op.xml new file mode 100644 index 0000000..2b03212 --- /dev/null +++ b/app/src/main/res/layout/activity_connection_op.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + +