mirror of https://github.com/ThmmyNoLife/mTHMMY
Ezerous
5 years ago
1597 changed files with 2537 additions and 1140 deletions
@ -0,0 +1,229 @@ |
|||||
|
<html> |
||||
|
<head> |
||||
|
<link rel="stylesheet" type="text/css" href="libraries_style.css" /> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<ul> |
||||
|
<li> |
||||
|
<h5><a href="https://github.com/junit-team/junit4">JUnit</a> v4.12 (Copyright © 2002-2019, JUnit)</h5> |
||||
|
</li> |
||||
|
</ul> |
||||
|
|
||||
|
|
||||
|
<br/> |
||||
|
<h4>Eclipse Public License v1.0</h4> |
||||
|
<pre> |
||||
|
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC |
||||
|
LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM |
||||
|
CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. |
||||
|
|
||||
|
1. DEFINITIONS |
||||
|
|
||||
|
"Contribution" means: |
||||
|
|
||||
|
a) in the case of the initial Contributor, the initial code and |
||||
|
documentation distributed under this Agreement, and |
||||
|
b) in the case of each subsequent Contributor: |
||||
|
|
||||
|
i) changes to the Program, and |
||||
|
|
||||
|
ii) additions to the Program; |
||||
|
|
||||
|
where such changes and/or additions to the Program originate from and are |
||||
|
distributed by that particular Contributor. A Contribution 'originates' from a |
||||
|
Contributor if it was added to the Program by such Contributor itself or anyone |
||||
|
acting on such Contributor's behalf. Contributions do not include additions to |
||||
|
the Program which: (i) are separate modules of software distributed in |
||||
|
conjunction with the Program under their own license agreement, and (ii) are |
||||
|
not derivative works of the Program. |
||||
|
|
||||
|
"Contributor" means any person or entity that distributes the Program. |
||||
|
|
||||
|
"Licensed Patents " mean patent claims licensable by a Contributor which are |
||||
|
necessarily infringed by the use or sale of its Contribution alone or when |
||||
|
combined with the Program. |
||||
|
|
||||
|
"Program" means the Contributions distributed in accordance with this Agreement. |
||||
|
|
||||
|
"Recipient" means anyone who receives the Program under this Agreement, |
||||
|
including all Contributors. |
||||
|
|
||||
|
2. GRANT OF RIGHTS |
||||
|
|
||||
|
a) Subject to the terms of this Agreement, each Contributor hereby grants |
||||
|
Recipient a non-exclusive, worldwide, royalty-free copyright license to |
||||
|
reproduce, prepare derivative works of, publicly display, publicly perform, |
||||
|
distribute and sublicense the Contribution of such Contributor, if any, and |
||||
|
such derivative works, in source code and object code form. |
||||
|
|
||||
|
b) Subject to the terms of this Agreement, each Contributor hereby grants |
||||
|
Recipient a non-exclusive, worldwide, royalty-free patent license under |
||||
|
Licensed Patents to make, use, sell, offer to sell, import and otherwise |
||||
|
transfer the Contribution of such Contributor, if any, in source code and |
||||
|
object code form. This patent license shall apply to the combination of the |
||||
|
Contribution and the Program if, at the time the Contribution is added by the |
||||
|
Contributor, such addition of the Contribution causes such combination to be |
||||
|
covered by the Licensed Patents. The patent license shall not apply to any |
||||
|
other combinations which include the Contribution. No hardware per se is |
||||
|
licensed hereunder. |
||||
|
|
||||
|
c) Recipient understands that although each Contributor grants the |
||||
|
licenses to its Contributions set forth herein, no assurances are provided by |
||||
|
any Contributor that the Program does not infringe the patent or other |
||||
|
intellectual property rights of any other entity. Each Contributor disclaims |
||||
|
any liability to Recipient for claims brought by any other entity based on |
||||
|
infringement of intellectual property rights or otherwise. As a condition to |
||||
|
exercising the rights and licenses granted hereunder, each Recipient hereby |
||||
|
assumes sole responsibility to secure any other intellectual property rights |
||||
|
needed, if any. For example, if a third party patent license is required to |
||||
|
allow Recipient to distribute the Program, it is Recipient's responsibility to |
||||
|
acquire that license before distributing the Program. |
||||
|
|
||||
|
d) Each Contributor represents that to its knowledge it has sufficient |
||||
|
copyright rights in its Contribution, if any, to grant the copyright license |
||||
|
set forth in this Agreement. |
||||
|
|
||||
|
3. REQUIREMENTS |
||||
|
|
||||
|
A Contributor may choose to distribute the Program in object code form under |
||||
|
its own license agreement, provided that: |
||||
|
|
||||
|
a) it complies with the terms and conditions of this Agreement; and |
||||
|
|
||||
|
b) its license agreement: |
||||
|
|
||||
|
i) effectively disclaims on behalf of all Contributors all warranties and |
||||
|
conditions, express and implied, including warranties or conditions of title |
||||
|
and non-infringement, and implied warranties or conditions of merchantability |
||||
|
and fitness for a particular purpose; |
||||
|
|
||||
|
ii) effectively excludes on behalf of all Contributors all liability for |
||||
|
damages, including direct, indirect, special, incidental and consequential |
||||
|
damages, such as lost profits; |
||||
|
|
||||
|
iii) states that any provisions which differ from this Agreement are |
||||
|
offered by that Contributor alone and not by any other party; and |
||||
|
|
||||
|
iv) states that source code for the Program is available from such |
||||
|
Contributor, and informs licensees how to obtain it in a reasonable manner on |
||||
|
or through a medium customarily used for software exchange. |
||||
|
|
||||
|
When the Program is made available in source code form: |
||||
|
|
||||
|
a) it must be made available under this Agreement; and |
||||
|
|
||||
|
b) a copy of this Agreement must be included with each copy of the |
||||
|
Program. |
||||
|
|
||||
|
Contributors may not remove or alter any copyright notices contained within the |
||||
|
Program. |
||||
|
|
||||
|
Each Contributor must identify itself as the originator of its Contribution, if |
||||
|
any, in a manner that reasonably allows subsequent Recipients to identify the |
||||
|
originator of the Contribution. |
||||
|
|
||||
|
4. COMMERCIAL DISTRIBUTION |
||||
|
|
||||
|
Commercial distributors of software may accept certain responsibilities with |
||||
|
respect to end users, business partners and the like. While this license is |
||||
|
intended to facilitate the commercial use of the Program, the Contributor who |
||||
|
includes the Program in a commercial product offering should do so in a manner |
||||
|
which does not create potential liability for other Contributors. Therefore, if |
||||
|
a Contributor includes the Program in a commercial product offering, such |
||||
|
Contributor ("Commercial Contributor") hereby agrees to defend and indemnify |
||||
|
every other Contributor ("Indemnified Contributor") against any losses, damages |
||||
|
and costs (collectively "Losses") arising from claims, lawsuits and other legal |
||||
|
actions brought by a third party against the Indemnified Contributor to the |
||||
|
extent caused by the acts or omissions of such Commercial Contributor in |
||||
|
connection with its distribution of the Program in a commercial product |
||||
|
offering. The obligations in this section do not apply to any claims or Losses |
||||
|
relating to any actual or alleged intellectual property infringement. In order |
||||
|
to qualify, an Indemnified Contributor must: a) promptly notify the Commercial |
||||
|
Contributor in writing of such claim, and b) allow the Commercial Contributor |
||||
|
to control, and cooperate with the Commercial Contributor in, the defense and |
||||
|
any related settlement negotiations. The Indemnified Contributor may |
||||
|
participate in any such claim at its own expense. |
||||
|
|
||||
|
For example, a Contributor might include the Program in a commercial product |
||||
|
offering, Product X. That Contributor is then a Commercial Contributor. If that |
||||
|
Commercial Contributor then makes performance claims, or offers warranties |
||||
|
related to Product X, those performance claims and warranties are such |
||||
|
Commercial Contributor's responsibility alone. Under this section, the |
||||
|
Commercial Contributor would have to defend claims against the other |
||||
|
Contributors related to those performance claims and warranties, and if a court |
||||
|
requires any other Contributor to pay any damages as a result, the Commercial |
||||
|
Contributor must pay those damages. |
||||
|
|
||||
|
5. NO WARRANTY |
||||
|
|
||||
|
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN |
||||
|
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR |
||||
|
IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, |
||||
|
NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each |
||||
|
Recipient is solely responsible for determining the appropriateness of using |
||||
|
and distributing the Program and assumes all risks associated with its exercise |
||||
|
of rights under this Agreement, including but not limited to the risks and |
||||
|
costs of program errors, compliance with applicable laws, damage to or loss of |
||||
|
data, programs or equipment, and unavailability or interruption of operations. |
||||
|
|
||||
|
6. DISCLAIMER OF LIABILITY |
||||
|
|
||||
|
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY |
||||
|
CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST |
||||
|
PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY |
||||
|
WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS |
||||
|
GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. |
||||
|
|
||||
|
7. GENERAL |
||||
|
|
||||
|
If any provision of this Agreement is invalid or unenforceable under applicable |
||||
|
law, it shall not affect the validity or enforceability of the remainder of the |
||||
|
terms of this Agreement, and without further action by the parties hereto, such |
||||
|
provision shall be reformed to the minimum extent necessary to make such |
||||
|
provision valid and enforceable. |
||||
|
|
||||
|
If Recipient institutes patent litigation against any |
||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging that the |
||||
|
Program itself (excluding combinations of the Program with other software or |
||||
|
hardware) infringes such Recipient's patent(s), then such Recipient's rights |
||||
|
granted under Section 2(b) shall terminate as of the date such litigation is |
||||
|
filed. |
||||
|
|
||||
|
All Recipient's rights under this Agreement shall terminate if it fails to |
||||
|
comply with any of the material terms or conditions of this Agreement and does |
||||
|
not cure such failure in a reasonable period of time after becoming aware of |
||||
|
such noncompliance. If all Recipient's rights under this Agreement terminate, |
||||
|
Recipient agrees to cease use and distribution of the Program as soon as |
||||
|
reasonably practicable. However, Recipient's obligations under this Agreement |
||||
|
and any licenses granted by Recipient relating to the Program shall continue |
||||
|
and survive. |
||||
|
|
||||
|
Everyone is permitted to copy and distribute copies of this Agreement, but in |
||||
|
order to avoid inconsistency the Agreement is copyrighted and may only be |
||||
|
modified in the following manner. The Agreement Steward reserves the right to |
||||
|
publish new versions (including revisions) of this Agreement from time to time. |
||||
|
No one other than the Agreement Steward has the right to modify this Agreement. |
||||
|
The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to |
||||
|
serve as the Agreement Steward to a suitable separate entity. Each new version |
||||
|
of the Agreement will be given a distinguishing version number. The Program |
||||
|
(including Contributions) may always be distributed subject to the version of |
||||
|
the Agreement under which it was received. In addition, after a new version of |
||||
|
the Agreement is published, Contributor may elect to distribute the Program |
||||
|
(including its Contributions) under the new version. Except as expressly stated |
||||
|
in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to |
||||
|
the intellectual property of any Contributor under this Agreement, whether |
||||
|
expressly, by implication, estoppel or otherwise. All rights in the Program not |
||||
|
expressly granted under this Agreement are reserved. |
||||
|
|
||||
|
This Agreement is governed by the laws of the State of New York and the |
||||
|
intellectual property laws of the United States of America. No party to this |
||||
|
Agreement will bring a legal action under this Agreement more than one year |
||||
|
after the cause of action arose. Each party waives its rights to a jury trial |
||||
|
in any resulting litigation. |
||||
|
</pre> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
@ -0,0 +1,28 @@ |
|||||
|
body { |
||||
|
font-family: sans-serif; |
||||
|
background-color: #333333; |
||||
|
} |
||||
|
|
||||
|
pre { |
||||
|
background-color: #3C3C3C; |
||||
|
color: #757575; |
||||
|
padding: 1em; |
||||
|
margin-left: 1em; |
||||
|
margin-right: 1em; |
||||
|
white-space: pre-wrap; |
||||
|
word-wrap: break-word; |
||||
|
} |
||||
|
|
||||
|
h4, h5 { |
||||
|
display: inline; |
||||
|
padding: 1em; |
||||
|
} |
||||
|
|
||||
|
a, h4, h5 { |
||||
|
color: #26A69A; |
||||
|
word-wrap: break-word; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
color: #26A69A; |
||||
|
} |
@ -0,0 +1,114 @@ |
|||||
|
<html> |
||||
|
<head> |
||||
|
<link rel="stylesheet" type="text/css" href="libraries_style.css" /> |
||||
|
</head> |
||||
|
|
||||
|
<body> |
||||
|
<ul> |
||||
|
<li> |
||||
|
<h5><a href="https://github.com/bumptech/glide">Glide</a> v4.11.0</h5> |
||||
|
</li> |
||||
|
</ul> |
||||
|
|
||||
|
|
||||
|
<br/> |
||||
|
<h4>Glide License</h4> |
||||
|
<pre> |
||||
|
License for everything not in third_party and not otherwise marked: |
||||
|
|
||||
|
Copyright 2014 Google, Inc. All rights reserved. |
||||
|
|
||||
|
Redistribution and use in source and binary forms, with or without modification, are |
||||
|
permitted provided that the following conditions are met: |
||||
|
|
||||
|
1. Redistributions of source code must retain the above copyright notice, this list of |
||||
|
conditions and the following disclaimer. |
||||
|
|
||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list |
||||
|
of conditions and the following disclaimer in the documentation and/or other materials |
||||
|
provided with the distribution. |
||||
|
|
||||
|
THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED |
||||
|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
||||
|
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR |
||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
|
||||
|
The views and conclusions contained in the software and documentation are those of the |
||||
|
authors and should not be interpreted as representing official policies, either expressed |
||||
|
or implied, of Google, Inc. |
||||
|
--------------------------------------------------------------------------------------------- |
||||
|
License for third_party/disklrucache: |
||||
|
|
||||
|
Copyright 2012 Jake Wharton |
||||
|
Copyright 2011 The Android Open Source Project |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
<a href="http://www.apache.org/licenses/">http://www.apache.org/licenses/</a> |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
--------------------------------------------------------------------------------------------- |
||||
|
License for third_party/gif_decoder: |
||||
|
|
||||
|
Copyright (c) 2013 Xcellent Creations, Inc. |
||||
|
|
||||
|
Permission is hereby granted, free of charge, to any person obtaining |
||||
|
a copy of this software and associated documentation files (the |
||||
|
"Software"), to deal in the Software without restriction, including |
||||
|
without limitation the rights to use, copy, modify, merge, publish, |
||||
|
distribute, sublicense, and/or sell copies of the Software, and to |
||||
|
permit persons to whom the Software is furnished to do so, subject to |
||||
|
the following conditions: |
||||
|
|
||||
|
The above copyright notice and this permission notice shall be |
||||
|
included in all copies or substantial portions of the Software. |
||||
|
|
||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
|
--------------------------------------------------------------------------------------------- |
||||
|
License for third_party/gif_encoder/AnimatedGifEncoder.java and |
||||
|
third_party/gif_encoder/LZWEncoder.java: |
||||
|
|
||||
|
No copyright asserted on the source code of this class. May be used for any |
||||
|
purpose, however, refer to the Unisys LZW patent for restrictions on use of |
||||
|
the associated LZWEncoder class. Please forward any corrections to |
||||
|
kweiner@fmsware.com. |
||||
|
|
||||
|
----------------------------------------------------------------------------- |
||||
|
License for third_party/gif_encoder/NeuQuant.java |
||||
|
|
||||
|
Copyright (c) 1994 Anthony Dekker |
||||
|
|
||||
|
NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See |
||||
|
"Kohonen neural networks for optimal colour quantization" in "Network: |
||||
|
Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of |
||||
|
the algorithm. |
||||
|
|
||||
|
Any party obtaining a copy of these files from the author, directly or |
||||
|
indirectly, is granted, free of charge, a full and unrestricted irrevocable, |
||||
|
world-wide, paid up, royalty-free, nonexclusive right and license to deal in |
||||
|
this software and documentation files (the "Software"), including without |
||||
|
limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
|
sublicense, and/or sell copies of the Software, and to permit persons who |
||||
|
receive copies from any such party to do so, with the only requirement being |
||||
|
that this copyright notice remain intact. |
||||
|
</pre> |
||||
|
</body> |
||||
|
|
||||
|
</html> |
After Width: | Height: | Size: 12 KiB |
@ -1,158 +0,0 @@ |
|||||
package gr.thmmy.mthmmy.activities.bookmarks; |
|
||||
|
|
||||
import android.app.Activity; |
|
||||
import android.graphics.Typeface; |
|
||||
import android.graphics.drawable.Drawable; |
|
||||
import android.os.Build; |
|
||||
import android.os.Bundle; |
|
||||
import android.view.LayoutInflater; |
|
||||
import android.view.View; |
|
||||
import android.view.ViewGroup; |
|
||||
import android.widget.ImageButton; |
|
||||
import android.widget.LinearLayout; |
|
||||
import android.widget.TextView; |
|
||||
|
|
||||
import androidx.annotation.NonNull; |
|
||||
import androidx.fragment.app.Fragment; |
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; |
|
||||
|
|
||||
import java.util.ArrayList; |
|
||||
|
|
||||
import gr.thmmy.mthmmy.R; |
|
||||
import gr.thmmy.mthmmy.model.Bookmark; |
|
||||
|
|
||||
/** |
|
||||
* A {@link Fragment} subclass. |
|
||||
* Use the {@link BookmarksTopicFragment#newInstance} factory method to |
|
||||
* create an instance of this fragment. |
|
||||
*/ |
|
||||
public class BookmarksTopicFragment extends Fragment { |
|
||||
private static final String ARG_SECTION_NUMBER = "SECTION_NUMBER"; |
|
||||
private static final String ARG_TOPIC_BOOKMARKS = "TOPIC_BOOKMARKS"; |
|
||||
|
|
||||
static final String INTERACTION_CLICK_TOPIC_BOOKMARK = "CLICK_TOPIC_BOOKMARK"; |
|
||||
static final String INTERACTION_TOGGLE_TOPIC_NOTIFICATION = "TOGGLE_TOPIC_NOTIFICATION"; |
|
||||
static final String INTERACTION_REMOVE_TOPIC_BOOKMARK = "REMOVE_TOPIC_BOOKMARK"; |
|
||||
|
|
||||
ArrayList<Bookmark> topicBookmarks = null; |
|
||||
|
|
||||
private static Drawable notificationsEnabledButtonImage; |
|
||||
private static Drawable notificationsDisabledButtonImage; |
|
||||
|
|
||||
// Required empty public constructor
|
|
||||
public BookmarksTopicFragment() { |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Use ONLY this factory method to create a new instance of |
|
||||
* this fragment using the provided parameters. |
|
||||
* |
|
||||
* @return A new instance of fragment Forum. |
|
||||
*/ |
|
||||
public static BookmarksTopicFragment newInstance(int sectionNumber, String topicBookmarks) { |
|
||||
BookmarksTopicFragment fragment = new BookmarksTopicFragment(); |
|
||||
Bundle args = new Bundle(); |
|
||||
args.putInt(ARG_SECTION_NUMBER, sectionNumber); |
|
||||
args.putString(ARG_TOPIC_BOOKMARKS, topicBookmarks); |
|
||||
fragment.setArguments(args); |
|
||||
return fragment; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onCreate(Bundle savedInstanceState) { |
|
||||
super.onCreate(savedInstanceState); |
|
||||
if (getArguments() != null) { |
|
||||
String bundledTopicBookmarks = getArguments().getString(ARG_TOPIC_BOOKMARKS); |
|
||||
if (bundledTopicBookmarks != null) { |
|
||||
topicBookmarks = Bookmark.stringToArrayList(bundledTopicBookmarks); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) |
|
||||
notificationsEnabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_on, null); |
|
||||
else |
|
||||
notificationsEnabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_on, null); |
|
||||
|
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) |
|
||||
notificationsDisabledButtonImage = getResources().getDrawable(R.drawable.ic_notification_off, null); |
|
||||
else |
|
||||
notificationsDisabledButtonImage = VectorDrawableCompat.create(getResources(), R.drawable.ic_notification_off, null); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public View onCreateView(@NonNull LayoutInflater layoutInflater, ViewGroup container, |
|
||||
Bundle savedInstanceState) { |
|
||||
// Inflates the layout for this fragment
|
|
||||
final View rootView = layoutInflater.inflate(R.layout.fragment_bookmarks, container, false); |
|
||||
//bookmarks_topic_container
|
|
||||
final LinearLayout bookmarksLinearView = rootView.findViewById(R.id.bookmarks_container); |
|
||||
|
|
||||
if(this.topicBookmarks != null && !this.topicBookmarks.isEmpty()) { |
|
||||
for (final Bookmark bookmarkedTopic : topicBookmarks) { |
|
||||
if (bookmarkedTopic != null && bookmarkedTopic.getTitle() != null) { |
|
||||
final LinearLayout row = (LinearLayout) layoutInflater.inflate( |
|
||||
R.layout.fragment_bookmarks_row, bookmarksLinearView, false); |
|
||||
row.setOnClickListener(view -> { |
|
||||
Activity activity = getActivity(); |
|
||||
if (activity instanceof BookmarksActivity) { |
|
||||
((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_CLICK_TOPIC_BOOKMARK, bookmarkedTopic); |
|
||||
} |
|
||||
}); |
|
||||
((TextView) row.findViewById(R.id.bookmark_title)).setText(bookmarkedTopic.getTitle()); |
|
||||
|
|
||||
final ImageButton notificationsEnabledButton = row.findViewById(R.id.toggle_notification); |
|
||||
if (!bookmarkedTopic.isNotificationsEnabled()) { |
|
||||
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); |
|
||||
} |
|
||||
|
|
||||
notificationsEnabledButton.setOnClickListener(view -> { |
|
||||
Activity activity = getActivity(); |
|
||||
if (activity instanceof BookmarksActivity) { |
|
||||
if (((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_TOGGLE_TOPIC_NOTIFICATION, bookmarkedTopic)) { |
|
||||
notificationsEnabledButton.setImageDrawable(notificationsEnabledButtonImage); |
|
||||
} else { |
|
||||
notificationsEnabledButton.setImageDrawable(notificationsDisabledButtonImage); |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
(row.findViewById(R.id.remove_bookmark)).setOnClickListener(view -> { |
|
||||
Activity activity = getActivity(); |
|
||||
if (activity instanceof BookmarksActivity) { |
|
||||
((BookmarksActivity) activity).onTopicInteractionListener(INTERACTION_REMOVE_TOPIC_BOOKMARK, bookmarkedTopic); |
|
||||
topicBookmarks.remove(bookmarkedTopic); |
|
||||
} |
|
||||
row.setVisibility(View.GONE); |
|
||||
|
|
||||
if (topicBookmarks.isEmpty()){ |
|
||||
bookmarksLinearView.addView(bookmarksListEmptyMessage()); |
|
||||
} |
|
||||
}); |
|
||||
bookmarksLinearView.addView(row); |
|
||||
} |
|
||||
} |
|
||||
} else { |
|
||||
bookmarksLinearView.addView(bookmarksListEmptyMessage()); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
return rootView; |
|
||||
} |
|
||||
|
|
||||
private TextView bookmarksListEmptyMessage() { |
|
||||
TextView emptyBookmarksCategory = new TextView(this.getContext()); |
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( |
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); |
|
||||
params.setMargins(0, 12, 0, 0); |
|
||||
emptyBookmarksCategory.setLayoutParams(params); |
|
||||
emptyBookmarksCategory.setText(getString(R.string.empty_topic_bookmarks)); |
|
||||
emptyBookmarksCategory.setTypeface(emptyBookmarksCategory.getTypeface(), Typeface.BOLD); |
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|
||||
emptyBookmarksCategory.setTextColor(this.getContext().getColor(R.color.primary_text)); |
|
||||
} else { |
|
||||
//noinspection deprecation
|
|
||||
emptyBookmarksCategory.setTextColor(this.getContext().getResources().getColor(R.color.primary_text)); |
|
||||
} |
|
||||
emptyBookmarksCategory.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); |
|
||||
return emptyBookmarksCategory; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,18 @@ |
|||||
|
package gr.thmmy.mthmmy.session; |
||||
|
|
||||
|
import android.os.AsyncTask; |
||||
|
|
||||
|
import gr.thmmy.mthmmy.base.BaseApplication; |
||||
|
|
||||
|
|
||||
|
public class ValidateSessionTask extends AsyncTask<String, Void, Void> { |
||||
|
@Override |
||||
|
protected Void doInBackground(String... params) { |
||||
|
BaseApplication.getInstance().getSessionManager().validateSession(); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public boolean isRunning(){ |
||||
|
return getStatus() == AsyncTask.Status.RUNNING; |
||||
|
} |
||||
|
} |
@ -1,47 +0,0 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
|
||||
|
|
||||
import android.graphics.Bitmap; |
|
||||
import android.graphics.BitmapShader; |
|
||||
import android.graphics.Canvas; |
|
||||
import android.graphics.Paint; |
|
||||
|
|
||||
import com.squareup.picasso.Transformation; |
|
||||
|
|
||||
/** |
|
||||
* Used as parameter for PICASSO library's {@link com.squareup.picasso.RequestCreator#transform(Transformation) transform} method. |
|
||||
* @see com.squareup.picasso.Picasso Picasso |
|
||||
*/ |
|
||||
public class CircleTransform implements Transformation { |
|
||||
@Override |
|
||||
public Bitmap transform(Bitmap source) { |
|
||||
int size = Math.min(source.getWidth(), source.getHeight()); |
|
||||
|
|
||||
int x = (source.getWidth() - size) / 2; |
|
||||
int y = (source.getHeight() - size) / 2; |
|
||||
|
|
||||
Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); |
|
||||
if (squaredBitmap != source) |
|
||||
source.recycle(); |
|
||||
|
|
||||
// For GIF images
|
|
||||
Bitmap.Config config = source.getConfig() != null ? source.getConfig() : Bitmap.Config.ARGB_8888; |
|
||||
Bitmap bitmap = Bitmap.createBitmap(size, size, config); |
|
||||
|
|
||||
Canvas canvas = new Canvas(bitmap); |
|
||||
Paint paint = new Paint(); |
|
||||
BitmapShader shader = new BitmapShader(squaredBitmap, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); |
|
||||
paint.setShader(shader); |
|
||||
paint.setAntiAlias(true); |
|
||||
|
|
||||
float r = size / 2f; |
|
||||
canvas.drawCircle(r, r, r, paint); |
|
||||
|
|
||||
squaredBitmap.recycle(); |
|
||||
return bitmap; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public String key() { |
|
||||
return "circle"; |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,73 @@ |
|||||
|
package gr.thmmy.mthmmy.utils; |
||||
|
|
||||
|
import androidx.annotation.VisibleForTesting; |
||||
|
|
||||
|
import static android.text.format.DateUtils.DAY_IN_MILLIS; |
||||
|
import static android.text.format.DateUtils.HOUR_IN_MILLIS; |
||||
|
import static android.text.format.DateUtils.MINUTE_IN_MILLIS; |
||||
|
import static android.text.format.DateUtils.SECOND_IN_MILLIS; |
||||
|
import static android.text.format.DateUtils.YEAR_IN_MILLIS; |
||||
|
|
||||
|
public class DateTimeUtils { |
||||
|
|
||||
|
private static final long MONTH_IN_MILLIS = 30*DAY_IN_MILLIS; |
||||
|
private static final long DECADE_IN_MILLIS = 10*YEAR_IN_MILLIS; |
||||
|
|
||||
|
@VisibleForTesting |
||||
|
public static String getRelativeTimeSpanString(long time) { |
||||
|
long now = System.currentTimeMillis(); |
||||
|
|
||||
|
boolean past = (now >= time); |
||||
|
long duration = Math.abs(now - time); |
||||
|
String format; |
||||
|
long count, mod; |
||||
|
if(duration < 45*SECOND_IN_MILLIS) |
||||
|
return "just now"; |
||||
|
else if (duration < 45*MINUTE_IN_MILLIS) { |
||||
|
count = duration/MINUTE_IN_MILLIS; |
||||
|
mod = duration % MINUTE_IN_MILLIS; |
||||
|
if(mod >= 30*SECOND_IN_MILLIS) |
||||
|
count += 1; |
||||
|
format = "%dm"; |
||||
|
} else if (duration < 22*HOUR_IN_MILLIS) { |
||||
|
count = duration/HOUR_IN_MILLIS; |
||||
|
format = "%dh"; |
||||
|
mod = (duration%HOUR_IN_MILLIS)/MINUTE_IN_MILLIS; |
||||
|
if(count<3 && mod>9 && mod<51){ |
||||
|
if(count==0) |
||||
|
format = mod +"m"; |
||||
|
else |
||||
|
format = format + " " + mod +"m"; |
||||
|
} |
||||
|
else if(mod >= 30) |
||||
|
count += 1; |
||||
|
} else if (duration < 26*DAY_IN_MILLIS) { |
||||
|
count = duration/DAY_IN_MILLIS; |
||||
|
format = "%dd"; |
||||
|
mod = duration % DAY_IN_MILLIS; |
||||
|
if(mod >= 12*HOUR_IN_MILLIS) |
||||
|
count += 1; |
||||
|
} else if (duration < 320*DAY_IN_MILLIS) { |
||||
|
count = duration/MONTH_IN_MILLIS; |
||||
|
format = "%d month"; |
||||
|
mod = duration % MONTH_IN_MILLIS; |
||||
|
if(mod >= 15*DAY_IN_MILLIS) |
||||
|
count += 1; |
||||
|
if(count>1) |
||||
|
format = format + 's'; |
||||
|
} else if (duration < DECADE_IN_MILLIS) { |
||||
|
count = duration/YEAR_IN_MILLIS; |
||||
|
format = "%d year"; |
||||
|
mod = duration % YEAR_IN_MILLIS; |
||||
|
if(mod >= 183*DAY_IN_MILLIS) |
||||
|
count += 1; |
||||
|
if(count>1) |
||||
|
format = format + 's'; |
||||
|
} |
||||
|
else |
||||
|
return past ? "a long time ago": "in the distant future"; |
||||
|
|
||||
|
format = past ? format : "in " + format; |
||||
|
return String.format(format, (int) count); |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.utils.crashreporting; |
||||
|
|
||||
import com.crashlytics.android.Crashlytics; |
import com.crashlytics.android.Crashlytics; |
||||
|
|
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.utils.crashreporting; |
||||
|
|
||||
import android.util.Log; |
import android.util.Log; |
||||
|
|
@ -0,0 +1,39 @@ |
|||||
|
package gr.thmmy.mthmmy.utils.io; |
||||
|
|
||||
|
import android.content.Context; |
||||
|
|
||||
|
import java.io.BufferedReader; |
||||
|
import java.io.IOException; |
||||
|
import java.io.InputStreamReader; |
||||
|
|
||||
|
import timber.log.Timber; |
||||
|
|
||||
|
public class AssetUtils { |
||||
|
public static String readFileToText(Context context, String fileName) { |
||||
|
StringBuilder stringBuilder = new StringBuilder(); |
||||
|
BufferedReader reader = null; |
||||
|
try { |
||||
|
reader = new BufferedReader(new InputStreamReader(context.getAssets().open(fileName))); |
||||
|
String line; |
||||
|
|
||||
|
while ((line = reader.readLine()) != null) { |
||||
|
stringBuilder.append(line); |
||||
|
stringBuilder.append("\n"); |
||||
|
} |
||||
|
return stringBuilder.toString(); |
||||
|
} catch (IOException e) { |
||||
|
Timber.e(e, "IO error reading file %s from assets.", fileName); |
||||
|
} catch (Exception e) { |
||||
|
Timber.e(e, "Error reading file %s from assets.", fileName); |
||||
|
} finally { |
||||
|
try { |
||||
|
if (reader != null) |
||||
|
reader.close(); |
||||
|
} catch (IOException e) { |
||||
|
Timber.e(e, "Error in AssetUtils (closing reader)."); |
||||
|
} |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,112 @@ |
|||||
|
package gr.thmmy.mthmmy.utils.parsing; |
||||
|
|
||||
|
import androidx.annotation.VisibleForTesting; |
||||
|
|
||||
|
import org.joda.time.DateTime; |
||||
|
import org.joda.time.DateTimeUtils; |
||||
|
import org.joda.time.DateTimeZone; |
||||
|
import org.joda.time.format.DateTimeFormat; |
||||
|
import org.joda.time.format.DateTimeFormatter; |
||||
|
import org.joda.time.format.DateTimeFormatterBuilder; |
||||
|
import org.joda.time.format.DateTimeParser; |
||||
|
|
||||
|
import java.text.DateFormatSymbols; |
||||
|
import java.util.Locale; |
||||
|
import java.util.regex.Matcher; |
||||
|
import java.util.regex.Pattern; |
||||
|
|
||||
|
import gr.thmmy.mthmmy.base.BaseApplication; |
||||
|
import timber.log.Timber; |
||||
|
|
||||
|
public class ThmmyDateTimeParser { |
||||
|
private static final DateTimeParser[] parsers = { |
||||
|
DateTimeFormat.forPattern("hh:mm:ss a").getParser(), |
||||
|
DateTimeFormat.forPattern("HH:mm:ss").getParser(), |
||||
|
DateTimeFormat.forPattern("MMMM d, Y, hh:mm:ss a").getParser(), |
||||
|
DateTimeFormat.forPattern("MMMM d, Y, HH:mm:ss").getParser(), |
||||
|
DateTimeFormat.forPattern("d MMMM Y, HH:mm:ss").getParser(), |
||||
|
DateTimeFormat.forPattern("Y-M-d, HH:mm:ss").getParser(), |
||||
|
DateTimeFormat.forPattern("d-M-Y, HH:mm:ss").getParser() |
||||
|
}; |
||||
|
|
||||
|
private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() |
||||
|
.append(null, parsers) |
||||
|
.toFormatter(); |
||||
|
|
||||
|
//TODO: Replace with Locale.forLanguageTag() (with "el-GR","en-US") when KitKat support is dropped
|
||||
|
private static final Locale greekLocale = new Locale("el", "GR"); |
||||
|
private static final Locale englishLocale = new Locale("en", "US"); |
||||
|
|
||||
|
private static final Pattern pattern = Pattern.compile("\\s((1[3-9]|2[0-3]):)"); |
||||
|
|
||||
|
private ThmmyDateTimeParser(){} |
||||
|
|
||||
|
public static String convertToTimestamp(String thmmyDateTime){ |
||||
|
Timber.v("Will attempt to convert %s to timestamp.", thmmyDateTime); |
||||
|
String originalDateTime = thmmyDateTime; |
||||
|
DateTimeZone dtz = getDtz(); |
||||
|
|
||||
|
// Remove any unnecessary "Today at" strings
|
||||
|
thmmyDateTime = purifyTodayDateTime(thmmyDateTime); |
||||
|
|
||||
|
// Add today's date for the first two cases
|
||||
|
if(thmmyDateTime.charAt(2)==':') |
||||
|
thmmyDateTime = (new DateTime()).toString("MMMM d, Y, ") + thmmyDateTime; |
||||
|
|
||||
|
// Don't even ask
|
||||
|
if(thmmyDateTime.contains("am")) |
||||
|
thmmyDateTime = thmmyDateTime.replaceAll("\\s00:"," 12:"); |
||||
|
|
||||
|
// For the stupid format 23:54:12 pm
|
||||
|
Matcher matcher = pattern.matcher(thmmyDateTime); |
||||
|
if (matcher.find()) |
||||
|
thmmyDateTime = thmmyDateTime.replaceAll("\\spm",""); |
||||
|
|
||||
|
DateTime dateTime; |
||||
|
try{ |
||||
|
dateTime=formatter.withZone(dtz).withLocale(englishLocale).parseDateTime(thmmyDateTime); |
||||
|
} |
||||
|
catch (IllegalArgumentException e1){ |
||||
|
Timber.v("Parsing DateTime %s using English Locale failed.", thmmyDateTime); |
||||
|
try{ |
||||
|
DateFormatSymbols dfs = DateTimeUtils.getDateFormatSymbols(greekLocale); |
||||
|
thmmyDateTime = thmmyDateTime.replace("am",dfs.getAmPmStrings()[0]); |
||||
|
thmmyDateTime = thmmyDateTime.replace("pm",dfs.getAmPmStrings()[1]); |
||||
|
Timber.v("Attempting to parse DateTime %s using Greek Locale...", thmmyDateTime); |
||||
|
dateTime=formatter.withZone(dtz).withLocale(greekLocale).parseDateTime(thmmyDateTime); |
||||
|
} |
||||
|
catch (IllegalArgumentException e2){ |
||||
|
Timber.d("Parsing DateTime %s using Greek Locale failed too.", thmmyDateTime); |
||||
|
Timber.e("Couldn't convert DateTime %s to timestamp!", originalDateTime); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
String timestamp = Long.toString(dateTime.getMillis()); |
||||
|
Timber.v("DateTime %s was converted to %s, or %s", originalDateTime, timestamp, dateTime.toString()); |
||||
|
|
||||
|
return timestamp; |
||||
|
} |
||||
|
|
||||
|
public static String simplifyDateTime(String dateTime){ |
||||
|
return removeSeconds(purifyTodayDateTime(dateTime)); |
||||
|
} |
||||
|
|
||||
|
// Converts e.g. Today at 12:16:48 -> 12:16:48, but October 03, 2019, 16:40:18 remains as is
|
||||
|
@VisibleForTesting |
||||
|
static String purifyTodayDateTime(String dateTime){ |
||||
|
return dateTime.replaceAll("(Today at |Σήμερα στις )(.+)", "$2"); |
||||
|
} |
||||
|
|
||||
|
// Converts e.g. 12:16:48 -> 12:16, October 03, 2019, 16:40:18 -> 12:16 October 03, 2019, 16:40
|
||||
|
private static String removeSeconds(String dateTime){ |
||||
|
return dateTime.replaceAll("(.*):\\d+($|\\s.*)", "$1$2"); |
||||
|
} |
||||
|
|
||||
|
@VisibleForTesting |
||||
|
private static DateTimeZone getDtz(){ |
||||
|
if(!BaseApplication.getInstance().getSessionManager().isLoggedIn()) |
||||
|
return DateTimeZone.forID("Europe/Athens"); |
||||
|
else |
||||
|
return DateTimeZone.getDefault(); |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.utils.ui; |
||||
|
|
||||
import android.graphics.Canvas; |
import android.graphics.Canvas; |
||||
import android.graphics.Paint; |
import android.graphics.Paint; |
@ -0,0 +1,65 @@ |
|||||
|
package gr.thmmy.mthmmy.utils.ui; |
||||
|
|
||||
|
import android.content.ClipData; |
||||
|
import android.content.ClipboardManager; |
||||
|
import android.content.Context; |
||||
|
import android.content.ContextWrapper; |
||||
|
import android.widget.Toast; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
import androidx.appcompat.app.AlertDialog; |
||||
|
|
||||
|
import java.net.MalformedURLException; |
||||
|
import java.net.URL; |
||||
|
|
||||
|
import gr.thmmy.mthmmy.R; |
||||
|
import gr.thmmy.mthmmy.base.BaseActivity; |
||||
|
import gr.thmmy.mthmmy.base.BaseApplication; |
||||
|
import gr.thmmy.mthmmy.model.ThmmyFile; |
||||
|
import timber.log.Timber; |
||||
|
|
||||
|
import static android.content.Context.CLIPBOARD_SERVICE; |
||||
|
|
||||
|
public class ImageDownloadDialogBuilder extends AlertDialog.Builder{ |
||||
|
private static final String[] colors = {"Copy image location", "Save Image"}; |
||||
|
|
||||
|
private Context context; |
||||
|
private String imageURL; |
||||
|
|
||||
|
public ImageDownloadDialogBuilder(@NonNull Context context, String imageURL) { |
||||
|
super(context); |
||||
|
this.context = context; |
||||
|
this.imageURL = imageURL; |
||||
|
|
||||
|
setItems(colors, (dialog, which) -> { |
||||
|
if(which == 0) |
||||
|
copyUrlToClipboard(); |
||||
|
else { |
||||
|
try { |
||||
|
getBaseActivity().downloadFile(new ThmmyFile(new URL(imageURL))); |
||||
|
} catch (MalformedURLException e) { |
||||
|
Timber.e(e, "Exception downloading image (MalformedURLException)"); |
||||
|
} catch (NullPointerException e) { |
||||
|
Timber.e(e, "Exception downloading image (NullPointerException)"); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void copyUrlToClipboard(){ |
||||
|
ClipboardManager clipboard = (ClipboardManager) BaseApplication.getInstance().getSystemService(CLIPBOARD_SERVICE); |
||||
|
ClipData clip = ClipData.newPlainText("ReactiveWebViewCopiedText", imageURL); |
||||
|
clipboard.setPrimaryClip(clip); |
||||
|
Toast.makeText(BaseApplication.getInstance().getApplicationContext(),context.getString(R.string.link_copied_msg),Toast.LENGTH_SHORT).show(); |
||||
|
} |
||||
|
|
||||
|
private BaseActivity getBaseActivity() { |
||||
|
Context baseActivityContext = context; |
||||
|
while (baseActivityContext instanceof ContextWrapper) { |
||||
|
if (context instanceof BaseActivity) |
||||
|
return (BaseActivity) context; |
||||
|
baseActivityContext = ((ContextWrapper)context).getBaseContext(); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
package gr.thmmy.mthmmy.utils.ui; |
||||
|
|
||||
|
import android.app.Dialog; |
||||
|
import android.content.Context; |
||||
|
import android.graphics.drawable.ColorDrawable; |
||||
|
import android.graphics.drawable.Drawable; |
||||
|
import android.view.ViewGroup; |
||||
|
import android.view.Window; |
||||
|
|
||||
|
import androidx.annotation.Nullable; |
||||
|
|
||||
|
import com.bumptech.glide.Glide; |
||||
|
import com.bumptech.glide.load.DataSource; |
||||
|
import com.bumptech.glide.load.engine.GlideException; |
||||
|
import com.bumptech.glide.request.RequestListener; |
||||
|
import com.bumptech.glide.request.target.Target; |
||||
|
import com.github.chrisbanes.photoview.PhotoView; |
||||
|
|
||||
|
import gr.thmmy.mthmmy.R; |
||||
|
import gr.thmmy.mthmmy.base.BaseApplication; |
||||
|
|
||||
|
public class PhotoViewUtils { |
||||
|
private final static int screenWidth = BaseApplication.getInstance().getWidthInPixels(); |
||||
|
private final static int screenHeight = BaseApplication.getInstance().getHeightInPixels(); |
||||
|
|
||||
|
public static void displayPhotoViewImage(Context context, String imageURL) { |
||||
|
Dialog builder = new Dialog(context); |
||||
|
builder.requestWindowFeature(Window.FEATURE_NO_TITLE); |
||||
|
builder.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); |
||||
|
builder.getWindow().setBackgroundDrawable( |
||||
|
new ColorDrawable(android.graphics.Color.TRANSPARENT)); |
||||
|
|
||||
|
PhotoView photoView = new PhotoView(context); |
||||
|
photoView.setLayoutParams(new ViewGroup.LayoutParams(screenWidth, screenHeight)); |
||||
|
Glide.with(context) |
||||
|
.load(imageURL) |
||||
|
.fitCenter() |
||||
|
.error(R.drawable.ic_file_not_found) |
||||
|
.listener(new RequestListener<Drawable>() { |
||||
|
@Override |
||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) { |
||||
|
photoView.setZoomable(false); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) { |
||||
|
photoView.setOnLongClickListener(v -> { |
||||
|
ImageDownloadDialogBuilder imageDownloadDialogBuilder = new ImageDownloadDialogBuilder(context, imageURL); |
||||
|
imageDownloadDialogBuilder.show(); |
||||
|
return false; |
||||
|
}); |
||||
|
return false; |
||||
|
} |
||||
|
}) |
||||
|
.into(photoView); |
||||
|
|
||||
|
builder.addContentView(photoView, new ViewGroup.LayoutParams( |
||||
|
ViewGroup.LayoutParams.MATCH_PARENT, |
||||
|
ViewGroup.LayoutParams.MATCH_PARENT)); |
||||
|
builder.show(); |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.utils.ui; |
||||
|
|
||||
import android.content.Context; |
import android.content.Context; |
||||
import android.util.AttributeSet; |
import android.util.AttributeSet; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.utils.ui; |
||||
|
|
||||
import android.animation.Animator; |
import android.animation.Animator; |
||||
import android.content.Context; |
import android.content.Context; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.views; |
||||
|
|
||||
import android.annotation.SuppressLint; |
import android.annotation.SuppressLint; |
||||
import android.content.Context; |
import android.content.Context; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.views; |
||||
|
|
||||
import android.content.Context; |
import android.content.Context; |
||||
|
|
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.views; |
||||
|
|
||||
import android.content.Context; |
import android.content.Context; |
||||
import android.util.AttributeSet; |
import android.util.AttributeSet; |
@ -0,0 +1,93 @@ |
|||||
|
package gr.thmmy.mthmmy.views; |
||||
|
|
||||
|
import android.content.ClipData; |
||||
|
import android.content.ClipboardManager; |
||||
|
import android.content.Context; |
||||
|
import android.util.AttributeSet; |
||||
|
import android.view.MotionEvent; |
||||
|
import android.webkit.WebView; |
||||
|
import android.widget.Toast; |
||||
|
|
||||
|
import gr.thmmy.mthmmy.R; |
||||
|
import gr.thmmy.mthmmy.base.BaseApplication; |
||||
|
import gr.thmmy.mthmmy.utils.ui.ImageDownloadDialogBuilder; |
||||
|
|
||||
|
import static android.content.Context.CLIPBOARD_SERVICE; |
||||
|
import static gr.thmmy.mthmmy.utils.ui.PhotoViewUtils.displayPhotoViewImage; |
||||
|
|
||||
|
public class ReactiveWebView extends WebView { |
||||
|
private final static long MAX_TOUCH_DURATION = 100; |
||||
|
private final Context context; |
||||
|
private long downTime; |
||||
|
|
||||
|
|
||||
|
public ReactiveWebView(Context context) { |
||||
|
super(context); |
||||
|
this.context = context; |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
public ReactiveWebView(Context context, AttributeSet attrs) { |
||||
|
super(context, attrs); |
||||
|
this.context = context; |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
public ReactiveWebView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
|
super(context, attrs, defStyleAttr); |
||||
|
this.context = context; |
||||
|
init(); |
||||
|
} |
||||
|
|
||||
|
private void init(){ |
||||
|
setOnLongClickListener(); |
||||
|
this.setVerticalScrollBarEnabled(false); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean onTouchEvent(MotionEvent event) { |
||||
|
switch (event.getAction()) { |
||||
|
case MotionEvent.ACTION_DOWN: |
||||
|
downTime = event.getEventTime(); |
||||
|
break; |
||||
|
case MotionEvent.ACTION_UP: |
||||
|
if(event.getEventTime() - downTime <= MAX_TOUCH_DURATION) |
||||
|
performClick(); |
||||
|
break; |
||||
|
default: |
||||
|
break; |
||||
|
} |
||||
|
return super.onTouchEvent(event); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean performClick() { |
||||
|
WebView.HitTestResult result = this.getHitTestResult(); |
||||
|
if(result.getType() == WebView.HitTestResult.IMAGE_TYPE){ |
||||
|
String imageURL = result.getExtra(); |
||||
|
displayPhotoViewImage(context, imageURL); |
||||
|
} |
||||
|
return super.performClick(); |
||||
|
} |
||||
|
|
||||
|
private void setOnLongClickListener(){ |
||||
|
this.setOnLongClickListener(v -> { |
||||
|
HitTestResult result = ReactiveWebView.this.getHitTestResult(); |
||||
|
if(result.getType() == HitTestResult.SRC_ANCHOR_TYPE) |
||||
|
copyUrlToClipboard(result.getExtra()); |
||||
|
else if(result.getType() == WebView.HitTestResult.IMAGE_TYPE) { |
||||
|
String imageURL = result.getExtra(); |
||||
|
ImageDownloadDialogBuilder builder = new ImageDownloadDialogBuilder(context,imageURL); |
||||
|
builder.show(); |
||||
|
} |
||||
|
return false; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void copyUrlToClipboard(String urlToCopy){ |
||||
|
ClipboardManager clipboard = (ClipboardManager) BaseApplication.getInstance().getSystemService(CLIPBOARD_SERVICE); |
||||
|
ClipData clip = ClipData.newPlainText("ReactiveWebViewCopiedText", urlToCopy); |
||||
|
clipboard.setPrimaryClip(clip); |
||||
|
Toast.makeText(BaseApplication.getInstance().getApplicationContext(),context.getString(R.string.link_copied_msg),Toast.LENGTH_SHORT).show(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,232 @@ |
|||||
|
package gr.thmmy.mthmmy.views; |
||||
|
|
||||
|
import android.annotation.SuppressLint; |
||||
|
import android.content.Context; |
||||
|
import android.content.res.TypedArray; |
||||
|
import android.os.Handler; |
||||
|
import android.os.Parcel; |
||||
|
import android.os.Parcelable; |
||||
|
import android.text.format.DateUtils; |
||||
|
import android.util.AttributeSet; |
||||
|
import android.view.View; |
||||
|
import android.widget.TextView; |
||||
|
|
||||
|
import java.lang.ref.WeakReference; |
||||
|
|
||||
|
import gr.thmmy.mthmmy.R; |
||||
|
|
||||
|
import static gr.thmmy.mthmmy.utils.DateTimeUtils.getRelativeTimeSpanString; |
||||
|
|
||||
|
/** |
||||
|
* A modified version of https://github.com/curioustechizen/android-ago
|
||||
|
*/ |
||||
|
@SuppressLint("AppCompatCustomView") |
||||
|
public class RelativeTimeTextView extends TextView { |
||||
|
|
||||
|
private static final long INITIAL_UPDATE_INTERVAL = DateUtils.MINUTE_IN_MILLIS; |
||||
|
|
||||
|
private long mReferenceTime; |
||||
|
private Handler mHandler = new Handler(); |
||||
|
private UpdateTimeRunnable mUpdateTimeTask; |
||||
|
private boolean isUpdateTaskRunning = false; |
||||
|
|
||||
|
public RelativeTimeTextView(Context context, AttributeSet attrs) { |
||||
|
super(context, attrs); |
||||
|
init(context, attrs); |
||||
|
} |
||||
|
|
||||
|
public RelativeTimeTextView(Context context, AttributeSet attrs, int defStyle) { |
||||
|
super(context, attrs, defStyle); |
||||
|
init(context, attrs); |
||||
|
} |
||||
|
|
||||
|
private void init(Context context, AttributeSet attrs) { |
||||
|
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, |
||||
|
R.styleable.RelativeTimeTextView, 0, 0); |
||||
|
String referenceTimeText; |
||||
|
try { |
||||
|
referenceTimeText = a.getString(R.styleable.RelativeTimeTextView_reference_time); |
||||
|
} finally { |
||||
|
a.recycle(); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
mReferenceTime = Long.valueOf(referenceTimeText); |
||||
|
} catch (NumberFormatException nfe) { |
||||
|
/* |
||||
|
* TODO: Better exception handling |
||||
|
*/ |
||||
|
mReferenceTime = -1L; |
||||
|
} |
||||
|
|
||||
|
setReferenceTime(mReferenceTime); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Sets the reference time for this view. At any moment, the view will render a relative time period relative to the time set here. |
||||
|
* <p/> |
||||
|
* This value can also be set with the XML attribute {@code reference_time} |
||||
|
* @param referenceTime The timestamp (in milliseconds since epoch) that will be the reference point for this view. |
||||
|
*/ |
||||
|
public void setReferenceTime(long referenceTime) { |
||||
|
this.mReferenceTime = referenceTime; |
||||
|
|
||||
|
/* |
||||
|
* Note that this method could be called when a row in a ListView is recycled. |
||||
|
* Hence, we need to first stop any currently running schedules (for example from the recycled view. |
||||
|
*/ |
||||
|
stopTaskForPeriodicallyUpdatingRelativeTime(); |
||||
|
|
||||
|
/* |
||||
|
* Instantiate a new runnable with the new reference time |
||||
|
*/ |
||||
|
initUpdateTimeTask(); |
||||
|
|
||||
|
/* |
||||
|
* Start a new schedule. |
||||
|
*/ |
||||
|
startTaskForPeriodicallyUpdatingRelativeTime(); |
||||
|
|
||||
|
/* |
||||
|
* Finally, update the text display. |
||||
|
*/ |
||||
|
updateTextDisplay(); |
||||
|
} |
||||
|
|
||||
|
private void updateTextDisplay() { |
||||
|
/* |
||||
|
* TODO: Validation, Better handling of negative cases |
||||
|
*/ |
||||
|
if (this.mReferenceTime == -1L) |
||||
|
return; |
||||
|
setText(getRelativeTimeSpanString(mReferenceTime)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onAttachedToWindow() { |
||||
|
super.onAttachedToWindow(); |
||||
|
startTaskForPeriodicallyUpdatingRelativeTime(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onDetachedFromWindow() { |
||||
|
super.onDetachedFromWindow(); |
||||
|
stopTaskForPeriodicallyUpdatingRelativeTime(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void onVisibilityChanged(View changedView, int visibility) { |
||||
|
super.onVisibilityChanged(changedView, visibility); |
||||
|
if (visibility == GONE || visibility == INVISIBLE) { |
||||
|
stopTaskForPeriodicallyUpdatingRelativeTime(); |
||||
|
} else { |
||||
|
startTaskForPeriodicallyUpdatingRelativeTime(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void startTaskForPeriodicallyUpdatingRelativeTime() { |
||||
|
if(mUpdateTimeTask.isDetached()) initUpdateTimeTask(); |
||||
|
mHandler.post(mUpdateTimeTask); |
||||
|
isUpdateTaskRunning = true; |
||||
|
} |
||||
|
|
||||
|
private void initUpdateTimeTask() { |
||||
|
mUpdateTimeTask = new UpdateTimeRunnable(this, mReferenceTime); |
||||
|
} |
||||
|
|
||||
|
private void stopTaskForPeriodicallyUpdatingRelativeTime() { |
||||
|
if(isUpdateTaskRunning) { |
||||
|
mUpdateTimeTask.detach(); |
||||
|
mHandler.removeCallbacks(mUpdateTimeTask); |
||||
|
isUpdateTaskRunning = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Parcelable onSaveInstanceState() { |
||||
|
Parcelable superState = super.onSaveInstanceState(); |
||||
|
SavedState ss = new SavedState(superState); |
||||
|
ss.referenceTime = mReferenceTime; |
||||
|
return ss; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onRestoreInstanceState(Parcelable state) { |
||||
|
if (!(state instanceof SavedState)) { |
||||
|
super.onRestoreInstanceState(state); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
SavedState ss = (SavedState)state; |
||||
|
mReferenceTime = ss.referenceTime; |
||||
|
super.onRestoreInstanceState(ss.getSuperState()); |
||||
|
} |
||||
|
|
||||
|
public static class SavedState extends BaseSavedState { |
||||
|
|
||||
|
private long referenceTime; |
||||
|
|
||||
|
public SavedState(Parcelable superState) { |
||||
|
super(superState); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void writeToParcel(Parcel dest, int flags) { |
||||
|
super.writeToParcel(dest, flags); |
||||
|
dest.writeLong(referenceTime); |
||||
|
} |
||||
|
|
||||
|
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { |
||||
|
public SavedState createFromParcel(Parcel in) { |
||||
|
return new SavedState(in); |
||||
|
} |
||||
|
|
||||
|
public SavedState[] newArray(int size) { |
||||
|
return new SavedState[size]; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
private SavedState(Parcel in) { |
||||
|
super(in); |
||||
|
referenceTime = in.readLong(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static class UpdateTimeRunnable implements Runnable { |
||||
|
|
||||
|
private long mRefTime; |
||||
|
private final WeakReference<RelativeTimeTextView> weakRefRttv; |
||||
|
|
||||
|
UpdateTimeRunnable(RelativeTimeTextView rttv, long refTime) { |
||||
|
this.mRefTime = refTime; |
||||
|
weakRefRttv = new WeakReference<>(rttv); |
||||
|
} |
||||
|
|
||||
|
boolean isDetached() { |
||||
|
return weakRefRttv.get() == null; |
||||
|
} |
||||
|
|
||||
|
void detach() { |
||||
|
weakRefRttv.clear(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void run() { |
||||
|
RelativeTimeTextView rttv = weakRefRttv.get(); |
||||
|
if (rttv == null) return; |
||||
|
long difference = Math.abs(System.currentTimeMillis() - mRefTime); |
||||
|
long interval = INITIAL_UPDATE_INTERVAL; |
||||
|
if (difference > DateUtils.WEEK_IN_MILLIS) { |
||||
|
interval = DateUtils.WEEK_IN_MILLIS; |
||||
|
} else if (difference > DateUtils.DAY_IN_MILLIS) { |
||||
|
interval = DateUtils.DAY_IN_MILLIS; |
||||
|
} else if (difference > DateUtils.HOUR_IN_MILLIS) { |
||||
|
interval = DateUtils.HOUR_IN_MILLIS; |
||||
|
} |
||||
|
rttv.updateTextDisplay(); |
||||
|
rttv.mHandler.postDelayed(this, interval); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.utils; |
package gr.thmmy.mthmmy.views; |
||||
import android.content.Context; |
import android.content.Context; |
||||
import android.util.AttributeSet; |
import android.util.AttributeSet; |
||||
|
|
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.editorview; |
package gr.thmmy.mthmmy.views.editorview; |
||||
|
|
||||
import android.animation.Animator; |
import android.animation.Animator; |
||||
import android.annotation.SuppressLint; |
import android.annotation.SuppressLint; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.editorview; |
package gr.thmmy.mthmmy.views.editorview; |
||||
|
|
||||
import android.view.inputmethod.InputConnection; |
import android.view.inputmethod.InputConnection; |
||||
|
|
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.editorview; |
package gr.thmmy.mthmmy.views.editorview; |
||||
|
|
||||
import android.content.Context; |
import android.content.Context; |
||||
import android.os.Handler; |
import android.os.Handler; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.editorview; |
package gr.thmmy.mthmmy.views.editorview; |
||||
|
|
||||
import android.graphics.drawable.AnimationDrawable; |
import android.graphics.drawable.AnimationDrawable; |
||||
import android.view.LayoutInflater; |
import android.view.LayoutInflater; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.editorview; |
package gr.thmmy.mthmmy.views.editorview; |
||||
|
|
||||
import android.view.LayoutInflater; |
import android.view.LayoutInflater; |
||||
import android.view.View; |
import android.view.View; |
@ -1,4 +1,4 @@ |
|||||
package gr.thmmy.mthmmy.editorview; |
package gr.thmmy.mthmmy.views.editorview; |
||||
|
|
||||
public interface IEmojiKeyboard { |
public interface IEmojiKeyboard { |
||||
/** |
/** |
@ -0,0 +1,5 @@ |
|||||
|
<vector android:height="96dp" android:tint="#D926A69A" |
||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0" |
||||
|
android:width="96dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
<path android:fillColor="#FF000000" android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z"/> |
||||
|
</vector> |
@ -0,0 +1,26 @@ |
|||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
android:width="108dp" |
||||
|
android:height="108dp" |
||||
|
android:viewportWidth="1039.6812" |
||||
|
android:viewportHeight="1039.6812"> |
||||
|
<group android:translateX="161.25058" |
||||
|
android:translateY="161.15057"> |
||||
|
<path |
||||
|
android:pathData="M0,358.69a358.58,358.69 0,1 0,717.16 0a358.58,358.69 0,1 0,-717.16 0z" |
||||
|
android:fillColor="#333"/> |
||||
|
<group> |
||||
|
<clip-path |
||||
|
android:pathData="M0,358.69a358.58,358.69 0,1 0,717.16 0a358.58,358.69 0,1 0,-717.16 0z"/> |
||||
|
<path |
||||
|
android:pathData="M358.66,367.61c1,0 26,-75 37,-108.48 1.54,-4.68 7,-5.52 8.66,-0.27L493.66,473.03a4.29,4.29 0,0 0,7.93 0l27.28,-66.77a4.1,4.1 0,0 1,4 -2.43c61.43,0.08 122.87,-0.08 184.31,0" |
||||
|
android:strokeWidth="28" |
||||
|
android:fillColor="#00000000" |
||||
|
android:strokeColor="#6bdad5"/> |
||||
|
<path |
||||
|
android:pathData="M358.66,367.61c-1,0 -26,-75 -37,-108.48 -1.54,-4.68 -7,-5.52 -8.66,-0.27L223.66,473.03a4.29,4.29 0,0 1,-7.93 0l-27.28,-66.77a4.1,4.1 0,0 0,-4 -2.43c-61.43,0.08 -122.87,-0.08 -184.31,0" |
||||
|
android:strokeWidth="28" |
||||
|
android:fillColor="#00000000" |
||||
|
android:strokeColor="#6bdad5"/> |
||||
|
</group> |
||||
|
</group> |
||||
|
</vector> |
@ -0,0 +1,5 @@ |
|||||
|
<vector android:height="24dp" android:tint="#FFFFFF" |
||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0" |
||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
|
<path android:fillColor="#FF000000" android:pathData="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM2,16h8v-2L2,14v2zM21.5,11.5L23,13l-6.99,7 -4.51,-4.5L13,14l3.01,3 5.49,-5.5z"/> |
||||
|
</vector> |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue