migrate to AndroidX
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / profiles / ProfileDetailFragment.java
1 /*
2  * Copyright © 2019 Damyan Ivanov.
3  * This file is part of MoLe.
4  * MoLe is free software: you can distribute it and/or modify it
5  * under the term of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your opinion), any later version.
8  *
9  * MoLe is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License terms for details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.ui.profiles;
19
20 import android.app.Activity;
21 import android.content.Context;
22 import android.os.Build;
23 import android.os.Bundle;
24 import androidx.annotation.ColorInt;
25 import androidx.annotation.NonNull;
26 import androidx.annotation.Nullable;
27 import com.google.android.material.appbar.CollapsingToolbarLayout;
28 import com.google.android.material.floatingactionbutton.FloatingActionButton;
29 import com.google.android.material.textfield.TextInputLayout;
30 import androidx.fragment.app.Fragment;
31 import android.text.Editable;
32 import android.text.TextWatcher;
33 import android.util.Log;
34 import android.view.LayoutInflater;
35 import android.view.Menu;
36 import android.view.MenuInflater;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.widget.AdapterView;
41 import android.widget.ArrayAdapter;
42 import android.widget.LinearLayout;
43 import android.widget.Spinner;
44 import android.widget.Switch;
45 import android.widget.TextView;
46
47 import net.ktnx.mobileledger.R;
48 import net.ktnx.mobileledger.model.Data;
49 import net.ktnx.mobileledger.model.MobileLedgerProfile;
50 import net.ktnx.mobileledger.ui.activity.ProfileDetailActivity;
51 import net.ktnx.mobileledger.ui.activity.ProfileListActivity;
52 import net.ktnx.mobileledger.utils.Colors;
53
54 import org.jetbrains.annotations.NotNull;
55
56 import java.util.List;
57
58 /**
59  * A fragment representing a single Profile detail screen.
60  * This fragment is either contained in a {@link ProfileListActivity}
61  * in two-pane mode (on tablets) or a {@link ProfileDetailActivity}
62  * on handsets.
63  */
64 public class ProfileDetailFragment extends Fragment {
65     /**
66      * The fragment argument representing the item ID that this fragment
67      * represents.
68      */
69     public static final String ARG_ITEM_ID = "item_id";
70
71     /**
72      * The dummy content this fragment is presenting.
73      */
74     private MobileLedgerProfile mProfile;
75     private TextView url;
76     private Switch postingPermitted;
77     private TextInputLayout urlLayout;
78     private LinearLayout authParams;
79     private Switch useAuthentication;
80     private TextView userName;
81     private TextInputLayout userNameLayout;
82     private TextView password;
83     private TextInputLayout passwordLayout;
84     private TextView profileName;
85     private TextInputLayout profileNameLayout;
86     private FloatingActionButton fab;
87     private Spinner colorSpinner;
88
89     /**
90      * Mandatory empty constructor for the fragment manager to instantiate the
91      * fragment (e.g. upon screen orientation changes).
92      */
93     public ProfileDetailFragment() {
94     }
95     @Override
96     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
97         Log.d("profiles", "[fragment] Creating profile details options menu");
98         super.onCreateOptionsMenu(menu, inflater);
99         inflater.inflate(R.menu.profile_details, menu);
100         final MenuItem menuDeleteProfile = menu.findItem(R.id.menuDelete);
101         menuDeleteProfile.setOnMenuItemClickListener(item -> {
102             Log.d("profiles", String.format("[fragment] removing profile %s", mProfile.getUuid()));
103             mProfile.removeFromDB();
104             Data.profiles.remove(mProfile);
105             if (Data.profile.get().equals(mProfile)) {
106                 Log.d("profiles", "[fragment] setting current profile to 0");
107                 Data.setCurrentProfile(Data.profiles.get(0));
108             }
109             return false;
110         });
111         menuDeleteProfile.setVisible((mProfile != null) && (Data.profiles.size() > 1));
112     }
113     @Override
114     public void onCreate(Bundle savedInstanceState) {
115         super.onCreate(savedInstanceState);
116
117         if ((getArguments() != null) && getArguments().containsKey(ARG_ITEM_ID)) {
118             int index = getArguments().getInt(ARG_ITEM_ID, -1);
119             if (index != -1) mProfile = Data.profiles.get(index);
120
121             Activity activity = this.getActivity();
122             if (activity == null) throw new AssertionError();
123             CollapsingToolbarLayout appBarLayout = activity.findViewById(R.id.toolbar_layout);
124             if (appBarLayout != null) {
125                 if (mProfile != null) appBarLayout.setTitle(mProfile.getName());
126                 else appBarLayout.setTitle(getResources().getString(R.string.new_profile_title));
127             }
128         }
129     }
130     @Override
131     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
132         super.onActivityCreated(savedInstanceState);
133         Activity context = getActivity();
134         if (context == null) return;
135
136         fab = context.findViewById(R.id.fab);
137         fab.setOnClickListener(v -> {
138             if (!checkValidity()) return;
139
140             if (mProfile != null) {
141                 mProfile.setName(profileName.getText());
142                 mProfile.setUrl(url.getText());
143                 mProfile.setPostingPermitted(postingPermitted.isChecked());
144                 mProfile.setAuthEnabled(useAuthentication.isChecked());
145                 mProfile.setAuthUserName(userName.getText());
146                 mProfile.setAuthPassword(password.getText());
147                 mProfile.setThemeId(colorSpinner.getSelectedItem());
148 //                Log.d("profiles", String.format("Selected item is %d", mProfile.getThemeId()));
149                 mProfile.storeInDB();
150                 Log.d("profiles", "profile stored in DB");
151                 Data.profiles.triggerItemChangedNotification(mProfile);
152
153
154                 if (mProfile.getUuid().equals(Data.profile.get().getUuid())) {
155                     // dummy update to notify the observers of the possibly new name/URL
156                     Data.profile.set(mProfile);
157                 }
158             }
159             else {
160                 mProfile =
161                         new MobileLedgerProfile(profileName.getText(), postingPermitted.isChecked(),
162                                 url.getText(), useAuthentication.isChecked(), userName.getText(),
163                                 password.getText(),
164                                 Integer.valueOf((String) colorSpinner.getSelectedItem()));
165                 mProfile.storeInDB();
166                 Data.profiles.add(mProfile);
167                 MobileLedgerProfile.storeProfilesOrder();
168
169                 // first profile ever?
170                 if (Data.profiles.getList().size() == 1) Data.profile.set(mProfile);
171             }
172
173             Activity activity = getActivity();
174             if (activity != null) activity.finish();
175         });
176
177         profileName.requestFocus();
178     }
179     @Override
180     public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
181                              Bundle savedInstanceState) {
182         View rootView = inflater.inflate(R.layout.profile_detail, container, false);
183
184         profileName = rootView.findViewById(R.id.profile_name);
185         profileNameLayout = rootView.findViewById(R.id.profile_name_layout);
186         url = rootView.findViewById(R.id.url);
187         urlLayout = rootView.findViewById(R.id.url_layout);
188         postingPermitted = rootView.findViewById(R.id.profile_permit_posting);
189         authParams = rootView.findViewById(R.id.auth_params);
190         useAuthentication = rootView.findViewById(R.id.enable_http_auth);
191         userName = rootView.findViewById(R.id.auth_user_name);
192         userNameLayout = rootView.findViewById(R.id.auth_user_name_layout);
193         password = rootView.findViewById(R.id.password);
194         passwordLayout = rootView.findViewById(R.id.password_layout);
195         colorSpinner = rootView.findViewById(R.id.colorSpinner);
196
197         ArrayAdapter<CharSequence> adapter = ColorListAdapter
198                 .createFromResource(rootView.getContext(), R.array.profile_colors,
199                         R.layout.color_selector_item);
200 //        Log.d("profiles", String.format("color count: %s", adapter.getCount()));
201         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
202         colorSpinner.setAdapter(adapter);
203         colorSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
204             @Override
205             public void onNothingSelected(AdapterView<?> parent) {
206
207             }
208             @Override
209             public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
210                 final int primaryColor;
211                 final int degrees =
212                         Integer.valueOf((String) (parent.getAdapter().getItem(position)));
213                 if (degrees < 0) {
214                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
215                         if (getActivity() != null) primaryColor = getResources()
216                                 .getColor(R.color.colorPrimary, getActivity().getTheme());
217                         else primaryColor = Colors.getPrimaryColorForHue(Colors.DEFAULT_HUE_DEG);
218                     }
219                     else {
220                         primaryColor = getResources().getColor(R.color.colorPrimary);
221                     }
222                 }
223                 else primaryColor = Colors.getPrimaryColorForHue(degrees);
224
225                 if (colorSpinner != null) {
226                     colorSpinner.setBackgroundColor(primaryColor);
227 //                    for (int i = 0; i < colorSpinner.getChildCount(); i++) {
228 //                        View v = colorSpinner.getChildAt(i);
229 //
230 //                        if (v instanceof TextView) {
231 //                            ((TextView) v).setTextColor(Color.TRANSPARENT);
232 //                        }
233 //                    }
234                 }
235             }
236         });
237
238         useAuthentication.setOnCheckedChangeListener((buttonView, isChecked) -> {
239             Log.d("profiles", isChecked ? "auth enabled " : "auth disabled");
240             authParams.setVisibility(isChecked ? View.VISIBLE : View.GONE);
241             if (isChecked) userName.requestFocus();
242         });
243
244         hookClearErrorOnFocusListener(profileName, profileNameLayout);
245         hookClearErrorOnFocusListener(url, urlLayout);
246         hookClearErrorOnFocusListener(userName, userNameLayout);
247         hookClearErrorOnFocusListener(password, passwordLayout);
248
249         if (mProfile != null) {
250             profileName.setText(mProfile.getName());
251             postingPermitted.setChecked(mProfile.isPostingPermitted());
252             url.setText(mProfile.getUrl());
253             useAuthentication.setChecked(mProfile.isAuthEnabled());
254             authParams.setVisibility(mProfile.isAuthEnabled() ? View.VISIBLE : View.GONE);
255             userName.setText(mProfile.isAuthEnabled() ? mProfile.getAuthUserName() : "");
256             password.setText(mProfile.isAuthEnabled() ? mProfile.getAuthPassword() : "");
257
258             colorSpinner.setSelection(0);
259             int i = 0;
260             int sought = mProfile.getThemeId();
261 //            Log.d("profiles", String.format("Looking for %d",sought));
262             while (i < adapter.getCount()) {
263                 int item = Integer.valueOf(String.valueOf(adapter.getItem(i)));
264 //                Log.d("profiles", String.format("Item %d is %d", i, item));
265                 if (item == sought) {
266                     colorSpinner.setSelection(i);
267                     break;
268                 }
269
270                 i++;
271             }
272         }
273         else {
274             profileName.setText("");
275             url.setText("");
276             postingPermitted.setChecked(true);
277             useAuthentication.setChecked(false);
278             authParams.setVisibility(View.GONE);
279             userName.setText("");
280             password.setText("");
281             colorSpinner.setSelection(0);
282         }
283
284         return rootView;
285     }
286     private void hookClearErrorOnFocusListener(TextView view, TextInputLayout layout) {
287         view.setOnFocusChangeListener((v, hasFocus) -> {
288             if (hasFocus) layout.setError(null);
289         });
290         view.addTextChangedListener(new TextWatcher() {
291             @Override
292             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
293             }
294             @Override
295             public void onTextChanged(CharSequence s, int start, int before, int count) {
296                 layout.setError(null);
297             }
298             @Override
299             public void afterTextChanged(Editable s) {
300             }
301         });
302     }
303     boolean checkValidity() {
304         boolean valid = true;
305
306         String val = String.valueOf(profileName.getText());
307         if (val.trim().isEmpty()) {
308             valid = false;
309             profileNameLayout.setError(getResources().getText(R.string.err_profile_name_empty));
310         }
311
312         val = String.valueOf(url.getText());
313         if (val.trim().isEmpty()) {
314             valid = false;
315             urlLayout.setError(getResources().getText(R.string.err_profile_url_empty));
316         }
317         if (useAuthentication.isChecked()) {
318             val = String.valueOf(userName.getText());
319             if (val.trim().isEmpty()) {
320                 valid = false;
321                 userNameLayout
322                         .setError(getResources().getText(R.string.err_profile_user_name_empty));
323             }
324
325             val = String.valueOf(password.getText());
326             if (val.trim().isEmpty()) {
327                 valid = false;
328                 passwordLayout
329                         .setError(getResources().getText(R.string.err_profile_password_empty));
330             }
331         }
332
333         return valid;
334     }
335     private class ColorListAdapter extends ArrayAdapter<String> {
336         public ColorListAdapter(@NonNull Context context, int resource) {
337             super(context, resource);
338         }
339         public ColorListAdapter(@NonNull Context context, int resource, int textViewResourceId) {
340             super(context, resource, textViewResourceId);
341         }
342         public ColorListAdapter(@NonNull Context context, int resource, @NonNull String[] objects) {
343             super(context, resource, objects);
344         }
345         public ColorListAdapter(@NonNull Context context, int resource, int textViewResourceId,
346                                 @NonNull String[] objects) {
347             super(context, resource, textViewResourceId, objects);
348         }
349         public ColorListAdapter(@NonNull Context context, int resource,
350                                 @NonNull List<String> objects) {
351             super(context, resource, objects);
352         }
353         public ColorListAdapter(@NonNull Context context, int resource, int textViewResourceId,
354                                 @NonNull List<String> objects) {
355             super(context, resource, textViewResourceId, objects);
356         }
357         @NotNull
358         @Override
359         public View getView(int position, View convertView, @NotNull ViewGroup parent) {
360             String hueStr = getItem(position);
361             int hue = (hueStr == null) ? -1 : Integer.valueOf(hueStr);
362             @ColorInt int primaryColor = Colors.getPrimaryColorForHue(hue);
363
364             View view = convertView;
365             if (convertView == null) {
366                 view = getLayoutInflater().inflate(R.layout.color_selector_item, parent);
367             }
368
369             view.setBackgroundColor(primaryColor);
370             return view;
371         }
372     }
373 }