From 2abb9ca5578efa6e73d2bca8fdb7338752754ea7 Mon Sep 17 00:00:00 2001 From: Bryson Steck Date: Wed, 9 Jun 2021 22:19:58 -0600 Subject: organization --- .../brysonsteck/wiimmfiwatcher/AboutFragment.java | 108 ------------- .../brysonsteck/wiimmfiwatcher/MainActivity.java | 2 + .../wiimmfiwatcher/WatchCodeAdapter.java | 84 ---------- .../wiimmfiwatcher/WatchCodeFragment.java | 174 -------------------- .../wiimmfiwatcher/fragments/AboutFragment.java | 111 +++++++++++++ .../wiimmfiwatcher/fragments/WatchCodeAdapter.java | 85 ++++++++++ .../fragments/WatchCodeFragment.java | 175 +++++++++++++++++++++ .../brysonsteck/wiimmfiwatcher/wiimmfi/Player.java | 22 +-- .../wiimmfiwatcher/wiimmfi/RoomAdapter.java | 121 -------------- .../wiimmfiwatcher/wiimmfi/RoomData.java | 2 +- .../wiimmfiwatcher/wiimmfi/RoomFragment.java | 88 ----------- .../wiimmfiwatcher/wiimmfi/WiimmfiActivity.java | 4 +- .../wiimmfi/fragments/RoomAdapter.java | 120 ++++++++++++++ .../wiimmfi/fragments/RoomFragment.java | 86 ++++++++++ 14 files changed, 592 insertions(+), 590 deletions(-) delete mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/AboutFragment.java delete mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeAdapter.java delete mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeFragment.java create mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/AboutFragment.java create mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeAdapter.java create mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeFragment.java delete mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomAdapter.java delete mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomFragment.java create mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomAdapter.java create mode 100644 app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomFragment.java (limited to 'app') diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/AboutFragment.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/AboutFragment.java deleted file mode 100644 index 87f3def..0000000 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/AboutFragment.java +++ /dev/null @@ -1,108 +0,0 @@ -package me.brysonsteck.wiimmfiwatcher; - -import android.annotation.SuppressLint; -import android.content.res.Configuration; -import android.graphics.Color; -import android.os.Bundle; -import android.text.Html; -import android.text.Spanned; -import android.text.method.LinkMovementMethod; -import android.transition.TransitionInflater; -import android.view.View; -import android.widget.ScrollView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import com.google.android.material.appbar.MaterialToolbar; - -public class AboutFragment extends Fragment { - View aboutButton; - MaterialToolbar toolbar; - ScrollView scrollView; - - public AboutFragment() { - super(R.layout.about_fragment); - } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - TransitionInflater inflater = TransitionInflater.from(requireContext()); - setEnterTransition(inflater.inflateTransition(R.transition.slide_right)); - setExitTransition(inflater.inflateTransition(R.transition.slide_right)); - } - - @SuppressLint("SetTextI18n") - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - scrollView = view.findViewById(R.id.about_view); - - int nightModeFlags = - getContext().getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK; - if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { - // Night mode is active, we're using dark theme - scrollView.setBackgroundColor(Color.parseColor("#151515")); - } - - aboutButton = getActivity().findViewById(R.id.about_button); - toolbar = getActivity().findViewById(R.id.toolbar); - toolbar.setTitle(R.string.about_fragment_title); - - TextView aboutWatcher = view.findViewById(R.id.about_watcher_text); - TextView aboutMe = view.findViewById(R.id.about_me_text); - TextView github = view.findViewById(R.id.github_text); - TextView contact = view.findViewById(R.id.contact_text); - TextView bugs = view.findViewById(R.id.bugs_text); - TextView license = view.findViewById(R.id.license_text); - TextView version = view.findViewById(R.id.version_text); - - aboutWatcher.setText(R.string.about_watcher); - - aboutMe.setText(R.string.about_me); - - github.setClickable(true); - github.setMovementMethod(LinkMovementMethod.getInstance()); - - github.setText(R.string.github); - - contact.setClickable(true); - contact.setMovementMethod(LinkMovementMethod.getInstance()); - - contact.setText(R.string.contact); - - bugs.setClickable(true); - bugs.setMovementMethod(LinkMovementMethod.getInstance()); - - bugs.setText(R.string.bugs); - - license.setClickable(true); - license.setMovementMethod(LinkMovementMethod.getInstance()); - - license.setText(R.string.license); - - version.setClickable(true); - version.setMovementMethod(LinkMovementMethod.getInstance()); - - Spanned version_text = Html.fromHtml(getResources().getString(R.string.version, BuildConfig.VERSION_NAME)); - - version.setText(version_text); - } - - @Override - public void onStop() { - super.onStop(); - aboutButton.setVisibility(View.VISIBLE); - toolbar.setTitle("Wiimmfi Watcher"); - } - @Override - public void onResume() { - super.onResume(); - aboutButton.setVisibility(View.INVISIBLE); - toolbar.setTitle(R.string.about_fragment_title); - } - -} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/MainActivity.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/MainActivity.java index 8084714..ee68742 100644 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/MainActivity.java +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/MainActivity.java @@ -10,6 +10,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.room.Room; import me.brysonsteck.wiimmfiwatcher.database.AppDatabase; +import me.brysonsteck.wiimmfiwatcher.fragments.AboutFragment; +import me.brysonsteck.wiimmfiwatcher.fragments.WatchCodeFragment; public class MainActivity extends AppCompatActivity { AppDatabase database; diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeAdapter.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeAdapter.java deleted file mode 100644 index 7034888..0000000 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeAdapter.java +++ /dev/null @@ -1,84 +0,0 @@ -package me.brysonsteck.wiimmfiwatcher; - -import android.app.ProgressDialog; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.databinding.ObservableArrayList; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.button.MaterialButton; -import com.google.android.material.textview.MaterialTextView; - -import java.util.ArrayList; - -import me.brysonsteck.wiimmfiwatcher.model.FriendCode; -import me.brysonsteck.wiimmfiwatcher.wiimmfi.WiimmfiActivity; - -public class WatchCodeAdapter extends RecyclerView.Adapter{ - ObservableArrayList entries; - Context context; - MaterialTextView errorText; - ProgressDialog progressBar; - ArrayList recentCodes; - - public WatchCodeAdapter(Context context, ObservableArrayList entries, - MaterialTextView errorText, ProgressDialog progressBar) { - this.context = context; - this.entries = entries; - this.errorText = errorText; - this.progressBar = progressBar; - this.recentCodes = new ArrayList<>(); - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recent_friend_codes_item, parent, false); - return new ViewHolder(view); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - String currentFC = entries.get(position).friendCode; - MaterialButton fcButton = holder.itemView.findViewById(R.id.recent_friend_code_button); - int nightModeFlags = - context.getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK; - if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { - // Night mode is active, we're using dark theme - fcButton.setBackgroundColor(Color.parseColor("#313131")); - } - fcButton.setText(currentFC); - fcButton.setOnClickListener(view -> { - progressBar.setCancelable(true); - progressBar.setMessage(holder.itemView.getResources().getString(R.string.locating_text, currentFC)); - progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); - progressBar.setProgress(0); - progressBar.setMax(100); - progressBar.show(); - errorText.setText(""); - Intent intent = new Intent(view.getContext(), WiimmfiActivity.class); - intent.putExtra("friendCode", currentFC); - context.startActivity(intent); - }); - } - - - @Override - public int getItemCount() { - return entries.size(); - } - - class ViewHolder extends RecyclerView.ViewHolder { - public ViewHolder(@NonNull View itemView) { - super(itemView); - } - } -} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeFragment.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeFragment.java deleted file mode 100644 index 01069b6..0000000 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/WatchCodeFragment.java +++ /dev/null @@ -1,174 +0,0 @@ -package me.brysonsteck.wiimmfiwatcher; - -import android.app.ProgressDialog; -import android.content.Intent; -import android.os.Bundle; -import android.transition.TransitionInflater; -import android.view.KeyEvent; -import android.view.View; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ProgressBar; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.databinding.ObservableList; -import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.textview.MaterialTextView; - -import me.brysonsteck.wiimmfiwatcher.model.FriendCode; -import me.brysonsteck.wiimmfiwatcher.viewmodel.FriendCodeViewModel; -import me.brysonsteck.wiimmfiwatcher.wiimmfi.WiimmfiActivity; - -public class WatchCodeFragment extends Fragment { - ProgressDialog progressBar; - - public WatchCodeFragment() { - super(R.layout.watch_code_fragment); - } - - public boolean isValidFriendCode(String friendCode) { - String[] friendCodeSplit = friendCode.split("-"); - boolean valid = false; - if (friendCodeSplit.length == 3) { - for (String friendCodePart : friendCodeSplit) { - valid = friendCodePart.length() == 4; - if (!valid) { - break; - } - } - } - return valid; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - TransitionInflater inflater = TransitionInflater.from(requireContext()); - setEnterTransition(inflater.inflateTransition(R.transition.fade)); - setExitTransition(inflater.inflateTransition(R.transition.fade)); - } - - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - FriendCodeViewModel viewModel = new ViewModelProvider(getActivity()).get(FriendCodeViewModel.class); - progressBar = new ProgressDialog(getContext(), R.style.AppCompatAlertDialogStyle); - - MaterialTextView errorText = view.findViewById(R.id.error_text); - WatchCodeAdapter adapter = new WatchCodeAdapter(getContext(), viewModel.getEntries(), errorText, progressBar); - viewModel.getEntries().addOnListChangedCallback(new ObservableList.OnListChangedCallback>() { - @Override - public void onChanged(ObservableList sender) { - getActivity().runOnUiThread(() -> { - adapter.notifyDataSetChanged(); - }); - } - - @Override - public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) { - getActivity().runOnUiThread(() -> { - adapter.notifyItemRangeChanged(positionStart, itemCount); - }); - } - - @Override - public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) { - getActivity().runOnUiThread(() -> { - adapter.notifyItemRangeInserted(positionStart, itemCount); // this is the only method that seems to be called - }); - } - - @Override - public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) { - getActivity().runOnUiThread(() -> { - adapter.notifyItemMoved(fromPosition, toPosition); - }); - } - - @Override - public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) { - getActivity().runOnUiThread(() -> { - adapter.notifyItemRangeRemoved(positionStart, itemCount); - }); - } - }); - RecyclerView recyclerView = view.findViewById(R.id.recent_friend_codes_recycler_view); - LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); - linearLayoutManager.setReverseLayout(true); - linearLayoutManager.setStackFromEnd(true); - recyclerView.setLayoutManager(linearLayoutManager); - recyclerView.setAdapter(adapter); - - EditText friendCode = view.findViewById(R.id.friend_code_edit_text); - Button watchButton = view.findViewById(R.id.watch_button); - watchButton.setOnClickListener(buttonClick -> { - startWiimmfiActivity( - view, - friendCode, - errorText, - watchButton, - viewModel - ); - watchButton.setText(R.string.watch); - }); - friendCode.setOnKeyListener(new View.OnKeyListener() - { - public boolean onKey(View view1, int keyCode, KeyEvent event) - { - if (event.getAction() == KeyEvent.ACTION_DOWN) - { - switch (keyCode) - { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: - startWiimmfiActivity( - view, - friendCode, - errorText, - watchButton, - viewModel - ); - return true; - default: - break; - } - } - return false; - } - }); - } - - @Override - public void onStop() { - super.onStop(); - if (progressBar.isShowing()) { progressBar.dismiss(); } - } - - public void startWiimmfiActivity(View view, EditText friendCode, MaterialTextView errorText, Button watchButton, FriendCodeViewModel viewModel) { - Intent intent = new Intent(view.getContext(), WiimmfiActivity.class); - if (!isValidFriendCode(friendCode.getText().toString())) { - errorText.setText(R.string.error_fc_syntax); - } else { - progressBar.setCancelable(false); - progressBar.setMessage(getResources().getString(R.string.locating_text, friendCode.getText())); - progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); - progressBar.show(); - - errorText.setText(""); - viewModel.saveFriendCode("", friendCode.getText().toString()); - intent.putExtra("friendCode", friendCode.getText().toString()); - ProgressBar p = view.findViewById(R.id.progressBar1); - if(p.getVisibility() != View.GONE){ // check if it is visible - p.setVisibility(View.GONE); // if not set it to visible - watchButton.setVisibility(View.VISIBLE); // use 1 or 2 as parameters.. arg0 is the view(your button) from the onclick listener - } - startActivity(intent); - } - } -} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/AboutFragment.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/AboutFragment.java new file mode 100644 index 0000000..3047e85 --- /dev/null +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/AboutFragment.java @@ -0,0 +1,111 @@ +package me.brysonsteck.wiimmfiwatcher.fragments; + +import android.annotation.SuppressLint; +import android.content.res.Configuration; +import android.graphics.Color; +import android.os.Bundle; +import android.text.Html; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.transition.TransitionInflater; +import android.view.View; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.google.android.material.appbar.MaterialToolbar; + +import me.brysonsteck.wiimmfiwatcher.BuildConfig; +import me.brysonsteck.wiimmfiwatcher.R; + +public class AboutFragment extends Fragment { + View aboutButton; + MaterialToolbar toolbar; + ScrollView scrollView; + + public AboutFragment() { + super(R.layout.about_fragment); + } + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TransitionInflater inflater = TransitionInflater.from(requireContext()); + setEnterTransition(inflater.inflateTransition(R.transition.slide_right)); + setExitTransition(inflater.inflateTransition(R.transition.slide_right)); + } + + @SuppressLint("SetTextI18n") + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + scrollView = view.findViewById(R.id.about_view); + + int nightModeFlags = + getContext().getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK; + if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { + // Night mode is active, we're using dark theme + scrollView.setBackgroundColor(Color.parseColor("#151515")); + } + + aboutButton = getActivity().findViewById(R.id.about_button); + toolbar = getActivity().findViewById(R.id.toolbar); + toolbar.setTitle(R.string.about_fragment_title); + + TextView aboutWatcher = view.findViewById(R.id.about_watcher_text); + TextView aboutMe = view.findViewById(R.id.about_me_text); + TextView github = view.findViewById(R.id.github_text); + TextView contact = view.findViewById(R.id.contact_text); + TextView bugs = view.findViewById(R.id.bugs_text); + TextView license = view.findViewById(R.id.license_text); + TextView version = view.findViewById(R.id.version_text); + + aboutWatcher.setText(R.string.about_watcher); + + aboutMe.setText(R.string.about_me); + + github.setClickable(true); + github.setMovementMethod(LinkMovementMethod.getInstance()); + + github.setText(R.string.github); + + contact.setClickable(true); + contact.setMovementMethod(LinkMovementMethod.getInstance()); + + contact.setText(R.string.contact); + + bugs.setClickable(true); + bugs.setMovementMethod(LinkMovementMethod.getInstance()); + + bugs.setText(R.string.bugs); + + license.setClickable(true); + license.setMovementMethod(LinkMovementMethod.getInstance()); + + license.setText(R.string.license); + + version.setClickable(true); + version.setMovementMethod(LinkMovementMethod.getInstance()); + + Spanned version_text = Html.fromHtml(getResources().getString(R.string.version, BuildConfig.VERSION_NAME)); + + version.setText(version_text); + } + + @Override + public void onStop() { + super.onStop(); + aboutButton.setVisibility(View.VISIBLE); + toolbar.setTitle("Wiimmfi Watcher"); + } + @Override + public void onResume() { + super.onResume(); + aboutButton.setVisibility(View.INVISIBLE); + toolbar.setTitle(R.string.about_fragment_title); + } + +} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeAdapter.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeAdapter.java new file mode 100644 index 0000000..400436b --- /dev/null +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeAdapter.java @@ -0,0 +1,85 @@ +package me.brysonsteck.wiimmfiwatcher.fragments; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.databinding.ObservableArrayList; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.button.MaterialButton; +import com.google.android.material.textview.MaterialTextView; + +import java.util.ArrayList; + +import me.brysonsteck.wiimmfiwatcher.R; +import me.brysonsteck.wiimmfiwatcher.model.FriendCode; +import me.brysonsteck.wiimmfiwatcher.wiimmfi.WiimmfiActivity; + +public class WatchCodeAdapter extends RecyclerView.Adapter{ + ObservableArrayList entries; + Context context; + MaterialTextView errorText; + ProgressDialog progressBar; + ArrayList recentCodes; + + public WatchCodeAdapter(Context context, ObservableArrayList entries, + MaterialTextView errorText, ProgressDialog progressBar) { + this.context = context; + this.entries = entries; + this.errorText = errorText; + this.progressBar = progressBar; + this.recentCodes = new ArrayList<>(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recent_friend_codes_item, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + String currentFC = entries.get(position).friendCode; + MaterialButton fcButton = holder.itemView.findViewById(R.id.recent_friend_code_button); + int nightModeFlags = + context.getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK; + if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { + // Night mode is active, we're using dark theme + fcButton.setBackgroundColor(Color.parseColor("#313131")); + } + fcButton.setText(currentFC); + fcButton.setOnClickListener(view -> { + progressBar.setCancelable(true); + progressBar.setMessage(holder.itemView.getResources().getString(R.string.locating_text, currentFC)); + progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); + progressBar.setProgress(0); + progressBar.setMax(100); + progressBar.show(); + errorText.setText(""); + Intent intent = new Intent(view.getContext(), WiimmfiActivity.class); + intent.putExtra("friendCode", currentFC); + context.startActivity(intent); + }); + } + + + @Override + public int getItemCount() { + return entries.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(@NonNull View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeFragment.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeFragment.java new file mode 100644 index 0000000..2687518 --- /dev/null +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/fragments/WatchCodeFragment.java @@ -0,0 +1,175 @@ +package me.brysonsteck.wiimmfiwatcher.fragments; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.transition.TransitionInflater; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.ObservableList; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.textview.MaterialTextView; + +import me.brysonsteck.wiimmfiwatcher.R; +import me.brysonsteck.wiimmfiwatcher.model.FriendCode; +import me.brysonsteck.wiimmfiwatcher.viewmodel.FriendCodeViewModel; +import me.brysonsteck.wiimmfiwatcher.wiimmfi.WiimmfiActivity; + +public class WatchCodeFragment extends Fragment { + ProgressDialog progressBar; + + public WatchCodeFragment() { + super(R.layout.watch_code_fragment); + } + + public boolean isValidFriendCode(String friendCode) { + String[] friendCodeSplit = friendCode.split("-"); + boolean valid = false; + if (friendCodeSplit.length == 3) { + for (String friendCodePart : friendCodeSplit) { + valid = friendCodePart.length() == 4; + if (!valid) { + break; + } + } + } + return valid; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + TransitionInflater inflater = TransitionInflater.from(requireContext()); + setEnterTransition(inflater.inflateTransition(R.transition.fade)); + setExitTransition(inflater.inflateTransition(R.transition.fade)); + } + + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + FriendCodeViewModel viewModel = new ViewModelProvider(getActivity()).get(FriendCodeViewModel.class); + progressBar = new ProgressDialog(getContext(), R.style.AppCompatAlertDialogStyle); + + MaterialTextView errorText = view.findViewById(R.id.error_text); + WatchCodeAdapter adapter = new WatchCodeAdapter(getContext(), viewModel.getEntries(), errorText, progressBar); + viewModel.getEntries().addOnListChangedCallback(new ObservableList.OnListChangedCallback>() { + @Override + public void onChanged(ObservableList sender) { + getActivity().runOnUiThread(() -> { + adapter.notifyDataSetChanged(); + }); + } + + @Override + public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) { + getActivity().runOnUiThread(() -> { + adapter.notifyItemRangeChanged(positionStart, itemCount); + }); + } + + @Override + public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) { + getActivity().runOnUiThread(() -> { + adapter.notifyItemRangeInserted(positionStart, itemCount); // this is the only method that seems to be called + }); + } + + @Override + public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) { + getActivity().runOnUiThread(() -> { + adapter.notifyItemMoved(fromPosition, toPosition); + }); + } + + @Override + public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) { + getActivity().runOnUiThread(() -> { + adapter.notifyItemRangeRemoved(positionStart, itemCount); + }); + } + }); + RecyclerView recyclerView = view.findViewById(R.id.recent_friend_codes_recycler_view); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); + linearLayoutManager.setReverseLayout(true); + linearLayoutManager.setStackFromEnd(true); + recyclerView.setLayoutManager(linearLayoutManager); + recyclerView.setAdapter(adapter); + + EditText friendCode = view.findViewById(R.id.friend_code_edit_text); + Button watchButton = view.findViewById(R.id.watch_button); + watchButton.setOnClickListener(buttonClick -> { + startWiimmfiActivity( + view, + friendCode, + errorText, + watchButton, + viewModel + ); + watchButton.setText(R.string.watch); + }); + friendCode.setOnKeyListener(new View.OnKeyListener() + { + public boolean onKey(View view1, int keyCode, KeyEvent event) + { + if (event.getAction() == KeyEvent.ACTION_DOWN) + { + switch (keyCode) + { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + startWiimmfiActivity( + view, + friendCode, + errorText, + watchButton, + viewModel + ); + return true; + default: + break; + } + } + return false; + } + }); + } + + @Override + public void onStop() { + super.onStop(); + if (progressBar.isShowing()) { progressBar.dismiss(); } + } + + public void startWiimmfiActivity(View view, EditText friendCode, MaterialTextView errorText, Button watchButton, FriendCodeViewModel viewModel) { + Intent intent = new Intent(view.getContext(), WiimmfiActivity.class); + if (!isValidFriendCode(friendCode.getText().toString())) { + errorText.setText(R.string.error_fc_syntax); + } else { + progressBar.setCancelable(false); + progressBar.setMessage(getResources().getString(R.string.locating_text, friendCode.getText())); + progressBar.setProgressStyle(ProgressDialog.STYLE_SPINNER); + progressBar.show(); + + errorText.setText(""); + viewModel.saveFriendCode("", friendCode.getText().toString()); + intent.putExtra("friendCode", friendCode.getText().toString()); + ProgressBar p = view.findViewById(R.id.progressBar1); + if(p.getVisibility() != View.GONE){ // check if it is visible + p.setVisibility(View.GONE); // if not set it to visible + watchButton.setVisibility(View.VISIBLE); // use 1 or 2 as parameters.. arg0 is the view(your button) from the onclick listener + } + startActivity(intent); + } + } +} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/Player.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/Player.java index fe9e072..257ee20 100644 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/Player.java +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/Player.java @@ -1,15 +1,15 @@ package me.brysonsteck.wiimmfiwatcher.wiimmfi; public class Player { - String rosterNumber; - String miiName; - String friendCode; - String role; - String loginRegion; - String roomMatch; - String world; - String connFail; - String vr; - String br; - boolean watching; + public String rosterNumber; + public String miiName; + public String friendCode; + public String role; + public String loginRegion; + public String roomMatch; + public String world; + public String connFail; + public String vr; + public String br; + public boolean watching; } diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomAdapter.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomAdapter.java deleted file mode 100644 index 5538869..0000000 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomAdapter.java +++ /dev/null @@ -1,121 +0,0 @@ -package me.brysonsteck.wiimmfiwatcher.wiimmfi; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Color; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.card.MaterialCardView; - -import org.jsoup.*; - -import java.util.ArrayList; - -import me.brysonsteck.wiimmfiwatcher.R; - -public class RoomAdapter extends RecyclerView.Adapter{ - String display; - String playerLink; - String header; - ArrayList players; - Context context; - - public RoomAdapter (String display, String playerLink, String header, ArrayList players, Context context) { - this.display = display; - this.playerLink = playerLink; - this.header = header; - this.players = players; - this.context = context; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.room_player_data_item, parent, false); - return new ViewHolder(view); - } - - @SuppressLint("ResourceAsColor") - @Override - public void onBindViewHolder(@NonNull RoomAdapter.ViewHolder holder, int position) { - MaterialCardView cardView = holder.itemView.findViewById(R.id.player_card_view); - TextView rosterNumber = holder.itemView.findViewById(R.id.roster_number); - TextView miiName = holder.itemView.findViewById(R.id.mii_names); - TextView variableDisplay = holder.itemView.findViewById(R.id.variable_side_data); - Player currentPlayer = players.get(position); - LinearLayout.LayoutParams cardViewParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - cardView.setLayoutParams(cardViewParams); - ViewGroup.MarginLayoutParams cardViewMarginParams = (ViewGroup.MarginLayoutParams) cardView.getLayoutParams(); - cardViewMarginParams.setMargins(40,40,40,40); - cardView.requestLayout(); - int nightModeFlags = - context.getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK; - if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { - // Night mode is active, we're using dark theme - cardView.setCardBackgroundColor(Color.parseColor("#313131")); - } - if (currentPlayer.watching) { - cardView.setCardBackgroundColor(Color.parseColor("#0D47A1")); - rosterNumber.setTextColor(Color.WHITE); - miiName.setTextColor(Color.WHITE); - variableDisplay.setTextColor(Color.WHITE); - } - rosterNumber.setText(currentPlayer.rosterNumber + ") "); - miiName.setText(currentPlayer.miiName); - - switch (display) { - case "fc": - variableDisplay.setText(currentPlayer.friendCode); - break; - case "roles": - variableDisplay.setText(currentPlayer.role); - break; - case "login_regions": - variableDisplay.setText(currentPlayer.loginRegion); - break; - case "room_match": - variableDisplay.setText(currentPlayer.roomMatch); - break; - case "world": - variableDisplay.setText(currentPlayer.world); - break; - case "conn_fail": - variableDisplay.setText(currentPlayer.connFail); - break; - case "vr_br": - variableDisplay.setText("VR: " + currentPlayer.vr + " / BR: " + currentPlayer.br); - break; - } - if (position + 1 == getItemCount()) { - cardViewMarginParams.setMargins(40,40,40,250); - cardView.requestLayout(); - } - - } - - @Override - public int getItemCount() { - if (players == null) { - return 0; - } else { - return players.size(); - } - } - - class ViewHolder extends RecyclerView.ViewHolder { - public ViewHolder(@NonNull View itemView) { - super(itemView); - } - } -} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomData.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomData.java index 60ad7df..b5f6aaf 100644 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomData.java +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomData.java @@ -14,7 +14,7 @@ public class RoomData { String roomHeader; String playerLink; String friendCode; - Exception error; + public Exception error; ArrayList players = new ArrayList<>(); String userAgent = "Wiimmfi Watcher for Android (https://github.com/brysonsteck/wiimmfi-watcher) Version " + BuildConfig.VERSION_NAME; diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomFragment.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomFragment.java deleted file mode 100644 index fc144aa..0000000 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/RoomFragment.java +++ /dev/null @@ -1,88 +0,0 @@ -package me.brysonsteck.wiimmfiwatcher.wiimmfi; - -import android.os.Bundle; -import android.os.Looper; -import android.view.View; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.appbar.MaterialToolbar; -import com.google.android.material.floatingactionbutton.FloatingActionButton; - -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; - -import me.brysonsteck.wiimmfiwatcher.R; - -public class RoomFragment extends Fragment { - String display; - String header; - String playerLink; - ArrayList players; - RoomData roomData; - MaterialToolbar toolbar; - - public RoomFragment(String friendCode, ArrayList players, String playerLink, String display, MaterialToolbar toolbar) { - super(R.layout.room_fragment); - this.roomData = new RoomData(players, friendCode); - new Thread(() -> { - this.header = roomData.getRoomHeader(); - }).start(); - this.display = display; - this.players = players; - this.playerLink = playerLink; - this.toolbar = toolbar; - } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - FloatingActionButton refreshButton = view.findViewById(R.id.refresh_button); - TextView headerTextView = view.findViewById(R.id.room_header_text); - if (header == null) { - headerTextView.setText(R.string.header_null_error); - toolbar.setNavigationIcon(null); - } - if (roomData.error != null) { - headerTextView.setText(getResources().getString(R.string.jsoup_error, roomData.error)); - toolbar.setNavigationIcon(null); - } - if (roomData.error == null && header != null) { - headerTextView.setText(header); - toolbar.setNavigationIcon(R.drawable.ic_baseline_menu_24); - } - RecyclerView recyclerView = view.findViewById(R.id.player_data_recycler_view); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - recyclerView.setAdapter(new RoomAdapter(display, playerLink, header, players, getContext())); - - refreshButton.setOnClickListener((buttonView) -> { - this.players.clear(); - this.header = ""; - this.roomData = roomData.refresh(); - RoomData newRoomData = roomData.refresh(); - this.players = roomData.getPlayers(); - header = newRoomData.getRoomHeader(); - if (header == null) { - headerTextView.setText(R.string.header_null_error); - toolbar.setNavigationIcon(null); - } - if (newRoomData.error instanceof java.net.SocketTimeoutException || newRoomData.error instanceof java.net.UnknownHostException) { - headerTextView.setText(getResources().getString(R.string.jsoup_error, roomData.error)); - toolbar.setNavigationIcon(null); - } - if (roomData.error == null && header != null) { - headerTextView.setText(header); - toolbar.setNavigationIcon(R.drawable.ic_baseline_menu_24); - } - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - recyclerView.setAdapter(new RoomAdapter(display, playerLink, header, players, getContext())); - - }); - } -} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/WiimmfiActivity.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/WiimmfiActivity.java index c0d4c9f..6936275 100644 --- a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/WiimmfiActivity.java +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/WiimmfiActivity.java @@ -5,7 +5,6 @@ import android.content.res.Configuration; import android.graphics.Color; import android.os.Bundle; import android.os.StrictMode; -import android.view.View; import androidx.appcompat.app.AppCompatActivity; import androidx.drawerlayout.widget.DrawerLayout; @@ -13,11 +12,10 @@ import androidx.drawerlayout.widget.DrawerLayout; import com.google.android.material.appbar.MaterialToolbar; import com.google.android.material.navigation.NavigationView; -import org.jsoup.*; - import java.util.ArrayList; import me.brysonsteck.wiimmfiwatcher.R; +import me.brysonsteck.wiimmfiwatcher.wiimmfi.fragments.RoomFragment; public class WiimmfiActivity extends AppCompatActivity { ArrayList players = new ArrayList<>(); diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomAdapter.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomAdapter.java new file mode 100644 index 0000000..2840ce6 --- /dev/null +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomAdapter.java @@ -0,0 +1,120 @@ +package me.brysonsteck.wiimmfiwatcher.wiimmfi.fragments; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.card.MaterialCardView; + +import java.util.ArrayList; + +import me.brysonsteck.wiimmfiwatcher.R; +import me.brysonsteck.wiimmfiwatcher.wiimmfi.Player; + +public class RoomAdapter extends RecyclerView.Adapter{ + String display; + String playerLink; + String header; + ArrayList players; + Context context; + + public RoomAdapter (String display, String playerLink, String header, ArrayList players, Context context) { + this.display = display; + this.playerLink = playerLink; + this.header = header; + this.players = players; + this.context = context; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.room_player_data_item, parent, false); + return new ViewHolder(view); + } + + @SuppressLint("ResourceAsColor") + @Override + public void onBindViewHolder(@NonNull RoomAdapter.ViewHolder holder, int position) { + MaterialCardView cardView = holder.itemView.findViewById(R.id.player_card_view); + TextView rosterNumber = holder.itemView.findViewById(R.id.roster_number); + TextView miiName = holder.itemView.findViewById(R.id.mii_names); + TextView variableDisplay = holder.itemView.findViewById(R.id.variable_side_data); + Player currentPlayer = players.get(position); + LinearLayout.LayoutParams cardViewParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + cardView.setLayoutParams(cardViewParams); + ViewGroup.MarginLayoutParams cardViewMarginParams = (ViewGroup.MarginLayoutParams) cardView.getLayoutParams(); + cardViewMarginParams.setMargins(40,40,40,40); + cardView.requestLayout(); + int nightModeFlags = + context.getResources().getConfiguration().uiMode & + Configuration.UI_MODE_NIGHT_MASK; + if (nightModeFlags == Configuration.UI_MODE_NIGHT_YES) { + // Night mode is active, we're using dark theme + cardView.setCardBackgroundColor(Color.parseColor("#313131")); + } + if (currentPlayer.watching) { + cardView.setCardBackgroundColor(Color.parseColor("#0D47A1")); + rosterNumber.setTextColor(Color.WHITE); + miiName.setTextColor(Color.WHITE); + variableDisplay.setTextColor(Color.WHITE); + } + rosterNumber.setText(currentPlayer.rosterNumber + ") "); + miiName.setText(currentPlayer.miiName); + + switch (display) { + case "fc": + variableDisplay.setText(currentPlayer.friendCode); + break; + case "roles": + variableDisplay.setText(currentPlayer.role); + break; + case "login_regions": + variableDisplay.setText(currentPlayer.loginRegion); + break; + case "room_match": + variableDisplay.setText(currentPlayer.roomMatch); + break; + case "world": + variableDisplay.setText(currentPlayer.world); + break; + case "conn_fail": + variableDisplay.setText(currentPlayer.connFail); + break; + case "vr_br": + variableDisplay.setText("VR: " + currentPlayer.vr + " / BR: " + currentPlayer.br); + break; + } + if (position + 1 == getItemCount()) { + cardViewMarginParams.setMargins(40,40,40,250); + cardView.requestLayout(); + } + + } + + @Override + public int getItemCount() { + if (players == null) { + return 0; + } else { + return players.size(); + } + } + + class ViewHolder extends RecyclerView.ViewHolder { + public ViewHolder(@NonNull View itemView) { + super(itemView); + } + } +} diff --git a/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomFragment.java b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomFragment.java new file mode 100644 index 0000000..d6326f7 --- /dev/null +++ b/app/src/main/java/me/brysonsteck/wiimmfiwatcher/wiimmfi/fragments/RoomFragment.java @@ -0,0 +1,86 @@ +package me.brysonsteck.wiimmfiwatcher.wiimmfi.fragments; + +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import java.util.ArrayList; + +import me.brysonsteck.wiimmfiwatcher.R; +import me.brysonsteck.wiimmfiwatcher.wiimmfi.Player; +import me.brysonsteck.wiimmfiwatcher.wiimmfi.RoomData; + +public class RoomFragment extends Fragment { + String display; + String header; + String playerLink; + ArrayList players; + RoomData roomData; + MaterialToolbar toolbar; + + public RoomFragment(String friendCode, ArrayList players, String playerLink, String display, MaterialToolbar toolbar) { + super(R.layout.room_fragment); + this.roomData = new RoomData(players, friendCode); + new Thread(() -> { + this.header = roomData.getRoomHeader(); + }).start(); + this.display = display; + this.players = players; + this.playerLink = playerLink; + this.toolbar = toolbar; + } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + FloatingActionButton refreshButton = view.findViewById(R.id.refresh_button); + TextView headerTextView = view.findViewById(R.id.room_header_text); + if (header == null) { + headerTextView.setText(R.string.header_null_error); + toolbar.setNavigationIcon(null); + } + if (roomData.error != null) { + headerTextView.setText(getResources().getString(R.string.jsoup_error, roomData.error)); + toolbar.setNavigationIcon(null); + } + if (roomData.error == null && header != null) { + headerTextView.setText(header); + toolbar.setNavigationIcon(R.drawable.ic_baseline_menu_24); + } + RecyclerView recyclerView = view.findViewById(R.id.player_data_recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setAdapter(new RoomAdapter(display, playerLink, header, players, getContext())); + + refreshButton.setOnClickListener((buttonView) -> { + this.players.clear(); + this.header = ""; + this.roomData = roomData.refresh(); + RoomData newRoomData = roomData.refresh(); + this.players = roomData.getPlayers(); + header = newRoomData.getRoomHeader(); + if (header == null) { + headerTextView.setText(R.string.header_null_error); + toolbar.setNavigationIcon(null); + } + if (newRoomData.error instanceof java.net.SocketTimeoutException || newRoomData.error instanceof java.net.UnknownHostException) { + headerTextView.setText(getResources().getString(R.string.jsoup_error, roomData.error)); + toolbar.setNavigationIcon(null); + } + if (roomData.error == null && header != null) { + headerTextView.setText(header); + toolbar.setNavigationIcon(R.drawable.ic_baseline_menu_24); + } + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setAdapter(new RoomAdapter(display, playerLink, header, players, getContext())); + + }); + } +} -- cgit v1.2.3