2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 18:07:55 +00:00

Add a desktop-like about app page

Show version, authors and useful links
This commit is contained in:
Maxim Leshchenko 2021-08-21 13:11:55 +00:00 committed by Nicolas Fella
parent 988ffe92ff
commit 049cb6d6a6
30 changed files with 1361 additions and 63 deletions

View File

@ -365,7 +365,32 @@
android:value="org.kde.kdeconnect.UserInterface.MainActivity" /> android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity> </activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.About.EasterEggActivity"
android:label="@string/easter_egg"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.About.AboutKDEActivity"
android:label="@string/about_kde"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.UserInterface.About.LicensesActivity"
android:label="@string/licenses"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM15,18L4,18v-4h11v4zM15,13L4,13L4,9h11v4zM20,18h-4L16,9h4v9z"/>
</vector>

View File

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="48"
android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#fcfcfc" android:pathData="m26.8172,6.058 l-5.9774,0.5722v24.6133l5.9142,-0.8904v-10.494l7.9499,11.6392 6.2324,-1.972 -8.1405,-11.1935 8.2042,-10.5576 -6.3594,-1.4621 -7.8865,10.5572zM13.2678,12.0558c-0.0675,0.0071 -0.1327,0.0369 -0.1834,0.0877l-2.346,2.3456c-0.0986,0.0989 -0.1172,0.2523 -0.0445,0.3721l2.7467,4.5238c-0.4872,0.819 -0.8776,1.7021 -1.1567,2.6336l-5.0426,1.0489c-0.1403,0.0291 -0.2414,0.1534 -0.2414,0.2973v3.3173c0,0.1403 0.096,0.2618 0.2312,0.2945l4.8943,1.1963c0.261,1.0789 0.6654,2.1016 1.2,3.0416l-2.833,4.32c-0.079,0.1208 -0.0625,0.2798 0.0396,0.3815l2.3452,2.3456c0.0986,0.0984 0.2526,0.1176 0.3729,0.0454l4.4393,-2.6961c0.872,0.5032 1.8124,0.8992 2.8063,1.1677l1.0358,4.98c0.0291,0.1412 0.1539,0.2414 0.2969,0.2414h3.3177c0.1395,0 0.2612,-0.0952 0.2945,-0.232l1.22,-4.9903c1.0246,-0.2765 1.9935,-0.6886 2.8869,-1.2147l4.3747,2.8684c0.1203,0.0792 0.2794,0.0633 0.3815,-0.0387l2.346,-2.3456c0.0992,-0.0992 0.117,-0.2526 0.0441,-0.3721l-1.597,-2.632 -0.5167,0.1634c-0.0753,0.0238 -0.1575,-0.0043 -0.2018,-0.0699 0,0 -1.0186,-1.4912 -2.3342,-3.4167 -1.5727,3.0779 -4.7723,5.1864 -8.4672,5.1864 -5.2496,0 -9.5055,-4.2561 -9.5055,-9.5059 0,-3.8619 2.3035,-7.1832 5.6102,-8.6711v-2.4523c-0.6018,0.2105 -1.1836,0.4644 -1.7362,0.7654 -0.0004,-0.0004 -0.0008,-0.0014 -0.0024,-0.0028l-4.4777,-2.9368c-0.0603,-0.0394 -0.1302,-0.0551 -0.1977,-0.0482z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="m26.8172,6.058 l-5.9774,0.5722v24.6133l5.9142,-0.8904v-10.494l7.9499,11.6392 6.2324,-1.972 -8.1405,-11.1935 8.2042,-10.5576 -6.3594,-1.4621 -7.8865,10.5572zM13.2678,12.0558c-0.0675,0.0071 -0.1327,0.0369 -0.1834,0.0877l-2.346,2.3456c-0.0986,0.0989 -0.1172,0.2523 -0.0445,0.3721l2.7467,4.5238c-0.4872,0.819 -0.8776,1.7021 -1.1567,2.6336l-5.0426,1.0489c-0.1403,0.0291 -0.2414,0.1534 -0.2414,0.2973v3.3173c0,0.1403 0.096,0.2618 0.2312,0.2945l4.8943,1.1963c0.261,1.0789 0.6654,2.1016 1.2,3.0416l-2.833,4.32c-0.079,0.1208 -0.0625,0.2798 0.0396,0.3815l2.3452,2.3456c0.0986,0.0984 0.2526,0.1176 0.3729,0.0454l4.4393,-2.6961c0.872,0.5032 1.8124,0.8992 2.8063,1.1677l1.0358,4.98c0.0291,0.1412 0.1539,0.2414 0.2969,0.2414h3.3177c0.1395,0 0.2612,-0.0952 0.2945,-0.232l1.22,-4.9903c1.0246,-0.2765 1.9935,-0.6886 2.8869,-1.2147l4.3747,2.8684c0.1203,0.0792 0.2794,0.0633 0.3815,-0.0387l2.346,-2.3456c0.0992,-0.0992 0.117,-0.2526 0.0441,-0.3721l-1.597,-2.632 -0.5167,0.1634c-0.0753,0.0238 -0.1575,-0.0043 -0.2018,-0.0699 0,0 -1.0186,-1.4912 -2.3342,-3.4167 -1.5727,3.0779 -4.7723,5.1864 -8.4672,5.1864 -5.2496,0 -9.5055,-4.2561 -9.5055,-9.5059 0,-3.8619 2.3035,-7.1832 5.6102,-8.6711v-2.4523c-0.6018,0.2105 -1.1836,0.4644 -1.7362,0.7654 -0.0004,-0.0004 -0.0008,-0.0014 -0.0024,-0.0028l-4.4777,-2.9368c-0.0603,-0.0394 -0.1302,-0.0551 -0.1977,-0.0482z"
android:fillColor="#fcfcfc"/>
</vector>

BIN
res/drawable/konqi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="?android:attr/selectableItemBackground"
android:baselineAligned="false"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:gravity="center_vertical"
android:paddingEnd="?android:attr/scrollbarSize"
android:paddingRight="?android:attr/scrollbarSize">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/about_person_list_item_entry_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="17sp"
tools:text="Konqi Konqueror" />
<TextView
android:id="@+id/about_person_list_item_entry_task"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone"
tools:text="Developer"
tools:visibility="visible" />
</LinearLayout>
<ImageButton
android:id="@+id/about_person_list_item_entry_visit_homepage_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="@dimen/default_gap"
android:layout_marginRight="@dimen/default_gap"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/visit_contributors_homepage"
android:src="@drawable/ic_baseline_web_24"
android:visibility="gone"
app:tint="@color/text_color"
tools:visibility="visible" />
<ImageButton
android:id="@+id/about_person_list_item_entry_email_button"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/email_contributor"
android:src="@drawable/ic_baseline_send_24"
android:visibility="gone"
app:tint="@color/text_color"
tools:visibility="visible" />
</LinearLayout>

View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context="org.kde.kdeconnect.UserInterface.About.AboutKDEActivity">
<include layout="@layout/toolbar" android:id="@+id/toolbar_layout" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/about_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:layout_margin="24dp"
android:text="@string/kde_be_free"
android:textAppearance="?attr/textAppearanceHeadline6"
app:drawableLeftCompat="@drawable/ic_kde_48dp"
app:drawableStartCompat="@drawable/ic_kde_48dp"
app:drawableTint="@color/text_color" />
<com.google.android.material.card.MaterialCardView
xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
card_view:cardBackgroundColor="@color/card_stroke_color"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">
<TextView
android:id="@+id/about_text_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:padding="12dp"
android:textIsSelectable="true"
tools:text="@string/about_kde_about" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
card_view:cardBackgroundColor="@color/card_stroke_color"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">
<TextView
android:id="@+id/report_bugs_or_wishes_text_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:padding="12dp"
android:textIsSelectable="true"
tools:text="@string/about_kde_report_bugs_or_wishes" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
card_view:cardBackgroundColor="@color/card_stroke_color"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">
<TextView
android:id="@+id/join_kde_text_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:padding="12dp"
android:textIsSelectable="true"
tools:text="@string/about_kde_join_kde" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
card_view:cardBackgroundColor="@color/card_stroke_color"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">
<TextView
android:id="@+id/support_kde_text_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:padding="12dp"
android:textIsSelectable="true"
tools:text="@string/about_kde_support_kde" />
</com.google.android.material.card.MaterialCardView>
<ImageView
android:layout_width="wrap_content"
android:layout_height="256dp"
android:layout_gravity="center"
android:contentDescription="@string/konqi"
android:paddingBottom="12dp"
android:src="@drawable/konqi" />
</LinearLayout>
</ScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/easter_egg_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1d99f3"
android:gravity="center">
<ImageView
android:id="@+id/logo"
android:layout_width="396dp"
android:layout_height="396dp"
android:src="@drawable/ic_kde_48dp"
android:contentDescription="@string/easter_egg" />
<TextView
android:id="@+id/angle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
tools:text="90°"/>
</LinearLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="org.kde.kdeconnect.UserInterface.About.LicensesActivity">
<include layout="@layout/toolbar" android:id="@+id/toolbar_layout" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/licenses_text"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fillViewport="true">
<LinearLayout
android:id="@+id/about_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:id="@+id/general_info_card"
android:background="?android:attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
card_view:cardBackgroundColor="@color/card_stroke_color"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp">
<LinearLayout
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<ImageView
android:id="@+id/app_icon"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="top"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:paddingBottom="4dp"
android:contentDescription="@string/kde_connect"
tools:src="@drawable/icon" />
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content"
android:layout_width="match_parent">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Application Name"
android:textAppearance="?attr/textAppearanceHeadline6" />
<TextView
android:id="@+id/app_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Version 1.xy.z" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/app_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:layout_margin="12dp"
tools:text="Application Description" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<org.kde.kdeconnect.UserInterface.About.AutoGridLayout
android:id="@+id/info_buttons_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:columnCount="3"
app:columnWidth="84dp"
app:changeColumnCountIfTheyHaveOnlyOneElement="true">
<FrameLayout
android:id="@+id/report_bug_button"
style="@style/AboutButton">
<TextView
android:text="@string/report_bug"
app:drawableTopCompat="@drawable/ic_baseline_bug_report_24"
style="@style/AboutButton.TextView" />
</FrameLayout>
<FrameLayout
android:id="@+id/donate_button"
style="@style/AboutButton">
<TextView
android:text="@string/donate"
app:drawableTopCompat="@drawable/ic_baseline_attach_money_24"
style="@style/AboutButton.TextView" />
</FrameLayout>
<FrameLayout
android:id="@+id/source_code_button"
style="@style/AboutButton">
<TextView
android:text="@string/source_code"
app:drawableTopCompat="@drawable/ic_baseline_code_24"
style="@style/AboutButton.TextView" />
</FrameLayout>
<FrameLayout
android:id="@+id/licenses_button"
style="@style/AboutButton">
<TextView
android:text="@string/licenses"
app:drawableTopCompat="@drawable/ic_baseline_gavel_24"
style="@style/AboutButton.TextView" />
</FrameLayout>
<FrameLayout
android:id="@+id/about_kde_button"
style="@style/AboutButton">
<TextView
android:text="@string/about_kde"
app:drawableTopCompat="@drawable/ic_kde_24dp"
style="@style/AboutButton.TextView" />
</FrameLayout>
<FrameLayout
android:id="@+id/website_button"
style="@style/AboutButton">
<TextView
android:text="@string/website"
app:drawableTopCompat="@drawable/ic_baseline_web_24"
style="@style/AboutButton.TextView" />
</FrameLayout>
</org.kde.kdeconnect.UserInterface.About.AutoGridLayout>
<com.google.android.material.card.MaterialCardView
xmlns:card_view="https://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
card_view:cardBackgroundColor="@color/card_stroke_color"
card_view:cardCornerRadius="12dp"
card_view:cardElevation="5dp"
card_view:cardUseCompatPadding="true">
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_vertical"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:padding="12dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/authors"
android:textAppearance="?attr/textAppearanceHeadline6" />
<org.kde.kdeconnect.UserInterface.About.AdapterLinearLayout
android:id="@+id/authors_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="0dp" />
<TextView
android:id="@+id/authors_footer_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:textSize="14sp"
android:layout_marginTop="8dp"
android:paddingStart="4dp"
android:paddingLeft="4dp"
tools:text="Everyone else who has contributed to KDE Connect over the years"
tools:ignore="RtlSymmetry" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:kdeconnect="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_rise_up"
android:icon="@drawable/ic_arrow_upward_black_24dp"
android:title="@string/rise_up"
android:iconTint="@color/text_color"
kdeconnect:showAsAction="ifRoom" />
<item
android:id="@+id/menu_rise_down"
android:icon="@drawable/ic_arrow_downward_black_24dp"
android:title="@string/rise_down"
android:iconTint="@color/text_color"
kdeconnect:showAsAction="ifRoom" />
</menu>

View File

@ -1,4 +1,12 @@
<resources> <resources>
<!-- A high-contrast color for important visual elements (for less important elements, use colorControlNormal instead) --> <!-- A high-contrast color for important visual elements (for less important elements, use colorControlNormal instead) -->
<attr name="colorHighContrast" format="color" /> <attr name="colorHighContrast" format="color" />
<declare-styleable name="AutoGridLayout">
<!-- The width of one column. Used to align -->
<attr name="columnWidth" format="dimension" />
<!-- I think that everything is clear from the name -->
<attr name="changeColumnCountIfTheyHaveOnlyOneElement" format="boolean" />
</declare-styleable>
</resources> </resources>

View File

@ -454,5 +454,68 @@
<string name="source_code">Source Code</string> <string name="source_code">Source Code</string>
<string name="source_code_url" translatable="false">https://invent.kde.org/network/kdeconnect-android/</string> <string name="source_code_url" translatable="false">https://invent.kde.org/network/kdeconnect-android/</string>
<string name="licenses">Licenses</string> <string name="licenses">Licenses</string>
<string name="website">Website</string>
<string name="website_url" translatable="false">https://kdeconnect.kde.org/</string>
<string name="copyright_statement" translatable="false">(С) 2013-2021 The KDE Connect Developers</string>
<string name="about">About</string>
<string name="authors">Authors</string>
<string name="thanks_to">Thanks To</string>
<string name="easter_egg">Easter Egg</string>
<string name="email_contributor">Email contributor\n%s</string>
<string name="visit_contributors_homepage">Visit contributor\'s homepage\n%s</string>
<string name="version">Version %s</string>
<string name="about_kde">About KDE</string>
<string name="kde_be_free">KDE - Be Free!</string>
<string name="kde">KDE</string>
<string name="konqi">Konqi</string>
<string name="rise_up">Rise Up</string>
<string name="rise_down">Rise Down</string>
<string name="app_description">Multi-platform app that allows your devices to communicate (e.g., your phone and your computer)</string>
<string name="about_kde_about"><![CDATA[
<h1>About</h1>
<p>KDE is a world-wide community of software engineers, artists, writers, translators and creators who are committed to <a href="https://www.gnu.org/philosophy/free-sw.html">Free Software</a> development. KDE produces the Plasma desktop environment, hundreds of applications, and the many software libraries that support them.</p>
<p>KDE is a cooperative enterprise: no single entity controls its direction or products. Instead, we work together to achieve the common goal of building the world\'s finest Free Software. Everyone is welcome to <a href="https://community.kde.org/Get_Involved">join and contribute</a> to KDE, including you.</p>
Visit <a href="https://www.kde.org/">https://www.kde.org/</a> for more information about the KDE community and the software we produce.
]]></string>
<string name="about_kde_report_bugs_or_wishes"><![CDATA[
<h1>Report Bugs or Wishes</h1>
<p>Software can always be improved, and the KDE team is ready to do so. However, you - the user - must tell us when something does not work as expected or could be done better.</p>
<p>KDE has a bug tracking system. Visit <a href="https://bugs.kde.org/">https://bugs.kde.org/</a> or use the \"Report Bug\" button from the about screen to report bugs.</p>
If you have a suggestion for improvement then you are welcome to use the bug tracking system to register your wish. Make sure you use the severity called \"Wishlist\".
]]></string>
<string name="about_kde_join_kde"><![CDATA[
<h1>Join KDE</h1>
<p>You do not have to be a software developer to be a member of the KDE team. You can join the national teams that translate program interfaces. You can provide graphics, themes, sounds, and improved documentation. You decide!</p>
<p>Visit <a href="https://community.kde.org/Get_Involved">https://community.kde.org/Get_Involved</a> for information on some projects in which you can participate.</p>
If you need more information or documentation, then a visit to <a href="https://techbase.kde.org/">https://techbase.kde.org/</a> will provide you with what you need.
]]></string>
<string name="about_kde_support_kde"><![CDATA[
<h1>Support KDE</h1>
<p>KDE software is and will always be available free of charge, however creating it is not free.</p>
<p>To support development the KDE community has formed the KDE e.V., a non-profit organization legally founded in Germany. KDE e.V. represents the KDE community in legal and financial matters. See <a href="https://ev.kde.org/">https://ev.kde.org/</a> for information on KDE e.V.</p>
<p>KDE benefits from many kinds of contributions, including financial. We use the funds to reimburse members and others for expenses they incur when contributing. Further funds are used for legal support and organizing conferences and meetings.</p>
<p>We would like to encourage you to support our efforts with a financial donation, using one of the ways described at <a href="https://www.kde.org/community/donations/">https://www.kde.org/community/donations/</a>.</p>
Thank you very much in advance for your support.
]]></string>
<!-- Authors tasks -->
<string name="maintainer_and_developer">Maintainer and developer</string>
<string name="developer">Developer</string>
<string name="apple_support">macOS support. Working on iOS support</string>
<string name="bug_fixes_and_general_improvements">Bug fixes and general improvements</string>
<string name="samoilenko_yuri_task">SFTP implementation, bug fixes and general improvements</string>
<string name="aniket_kumar_task">SMS plugin improvements</string>
<string name="alex_fiestas_task">Contacts plugin improvements</string>
<string name="maxim_leshchenko_task">UI improvements and this about page</string>
<string name="holger_kaelberer_task">Remote keyboard plugin and bug fixes</string>
<string name="saikrishna_arcot_task">Support for using the keyboard in the remote input plugin, bug fixes and general improvements</string>
<string name="everyone_else">Everyone else who has contributed to KDE Connect over the years</string>
</resources> </resources>

View File

@ -63,4 +63,20 @@
<item name="android:windowIsFloating">true</item> <item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">false</item> <item name="android:backgroundDimEnabled">false</item>
</style> </style>
<style name="AboutButton">
<item name="android:layout_width">84dp</item>
<item name="android:layout_height">84dp</item>
<item name="android:layout_margin">4dp</item>
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
</style>
<style name="AboutButton.TextView">
<item name="android:layout_width">84dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">center</item>
<item name="android:drawablePadding">4dp</item>
<item name="android:gravity">center</item>
<item name="android:textSize">12sp</item>
<item name="drawableTint">@color/text_color</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
class AboutData(var name: String, var description: Int, var icon: Int, var versionName: String, var copyrightStatement: String? = null,
var bugURL: String? = null, var websiteURL: String? = null, var sourceCodeURL: String? = null, var donateURL: String? = null,
var authorsFooterText: Int? = null) : Parcelable {
val authors: MutableList<AboutPerson> = mutableListOf()
constructor(parcel: Parcel) : this(parcel.readString()!!, parcel.readInt(), parcel.readInt(), parcel.readString()!!, parcel.readString(),
parcel.readString(), parcel.readString(), parcel.readString(), parcel.readString(),
if (parcel.readByte() == 0x01.toByte()) parcel.readInt() else null) {
parcel.readList(authors as List<*>, AboutPerson::class.java.classLoader)
}
fun getDescriptionString(context: Context): String = context.resources.getString(description)
companion object CREATOR : Parcelable.Creator<AboutData> {
override fun createFromParcel(parcel: Parcel): AboutData = AboutData(parcel)
override fun newArray(size: Int): Array<AboutData?> = arrayOfNulls(size)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeInt(description)
parcel.writeInt(icon)
parcel.writeString(versionName)
parcel.writeString(copyrightStatement)
parcel.writeList(authors.toList())
parcel.writeString(bugURL)
parcel.writeString(websiteURL)
parcel.writeString(sourceCodeURL)
parcel.writeString(donateURL)
if (authorsFooterText == null) {
parcel.writeByte(0x00)
} else {
parcel.writeByte(0x01)
parcel.writeInt(authorsFooterText!!)
}
}
override fun describeContents(): Int = 0
}

View File

@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import org.kde.kdeconnect.UserInterface.List.ListAdapter
import org.kde.kdeconnect.UserInterface.MainActivity
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.FragmentAboutBinding
class AboutFragment : Fragment() {
private var binding: FragmentAboutBinding? = null
private lateinit var aboutData: AboutData
private var tapCount = 0
private var firstTapMillis: Long? = null
companion object {
@JvmStatic
fun newInstance(aboutData: AboutData): Fragment {
val fragment = AboutFragment()
val args = Bundle(1)
args.putParcelable("ABOUT_DATA", aboutData)
fragment.arguments = args
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
if (activity != null) {
(requireActivity() as MainActivity).supportActionBar?.setTitle(R.string.about)
}
aboutData = requireArguments().getParcelable("ABOUT_DATA")!!
binding = FragmentAboutBinding.inflate(inflater, container, false)
updateData()
return binding!!.root
}
@SuppressLint("SetTextI18n")
fun updateData() {
// Update general info
binding!!.appName.text = aboutData.name
binding!!.appDescription.text = this.context?.let { aboutData.getDescriptionString(it) } + if (aboutData.copyrightStatement == null) "" else "\n\n" + aboutData.copyrightStatement
binding!!.appIcon.setImageDrawable(this.context?.let { ContextCompat.getDrawable(it, aboutData.icon) })
binding!!.appVersion.text = this.context?.getString(R.string.version, aboutData.versionName)
// Setup Easter Egg onClickListener
binding!!.generalInfoCard.setOnClickListener {
if (firstTapMillis == null) {
firstTapMillis = System.currentTimeMillis()
}
if (++tapCount == 3) {
tapCount = 0
if (firstTapMillis!! >= (System.currentTimeMillis() - 500)) {
startActivity(Intent(context, EasterEggActivity::class.java))
}
firstTapMillis = null
}
}
// Update button onClickListeners
setupInfoButton(aboutData.bugURL, binding!!.reportBugButton)
setupInfoButton(aboutData.donateURL, binding!!.donateButton)
setupInfoButton(aboutData.sourceCodeURL, binding!!.sourceCodeButton)
binding!!.licensesButton.setOnClickListener {
startActivity(Intent(context, LicensesActivity::class.java))
}
binding!!.aboutKdeButton.setOnClickListener {
startActivity(Intent(context, AboutKDEActivity::class.java))
}
setupInfoButton(aboutData.websiteURL, binding!!.websiteButton)
// Update authors
binding!!.authorsList.adapter = ListAdapter(this.context, aboutData.authors.map { AboutPersonEntryItem(it) }, false)
if (aboutData.authorsFooterText != null) {
binding!!.authorsFooterText.text = context?.getString(aboutData.authorsFooterText!!)
}
}
private fun setupInfoButton(url: String?, button: FrameLayout) {
if (url == null) {
button.visibility = View.GONE
} else {
button.setOnClickListener {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}

View File

@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.os.Bundle
import android.text.Html
import android.text.Spanned
import android.text.method.LinkMovementMethod
import androidx.appcompat.app.AppCompatActivity
import org.kde.kdeconnect.UserInterface.ThemeUtil
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.ActivityAboutKdeBinding
class AboutKDEActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
ThemeUtil.setUserPreferredTheme(this)
super.onCreate(savedInstanceState)
val binding = ActivityAboutKdeBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbarLayout.toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setDisplayShowHomeEnabled(true)
binding.aboutTextView.text = fromHtml(resources.getString(R.string.about_kde_about))
binding.reportBugsOrWishesTextView.text = fromHtml(resources.getString(R.string.about_kde_report_bugs_or_wishes))
binding.joinKdeTextView.text = fromHtml(resources.getString(R.string.about_kde_join_kde))
binding.supportKdeTextView.text = fromHtml(resources.getString(R.string.about_kde_support_kde))
binding.aboutTextView.movementMethod = LinkMovementMethod.getInstance()
binding.reportBugsOrWishesTextView.movementMethod = LinkMovementMethod.getInstance()
binding.joinKdeTextView.movementMethod = LinkMovementMethod.getInstance()
binding.supportKdeTextView.movementMethod = LinkMovementMethod.getInstance()
}
private fun fromHtml(html: String): Spanned {
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY)
} else {
@Suppress("DEPRECATION") Html.fromHtml(html)
}
}
}

View File

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.os.Parcel
import android.os.Parcelable
class AboutPerson @JvmOverloads constructor(val name: String, val task: Int? = null, val emailAddress: String? = null, val webAddress: String? = null) : Parcelable {
constructor(parcel: Parcel) : this(parcel.readString().toString(), if (parcel.readByte() == 0x01.toByte()) parcel.readInt() else null, parcel.readString(), parcel.readString())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
if (task != null) {
parcel.writeByte(0x01)
parcel.writeInt(task)
} else {
parcel.writeByte(0x00)
}
parcel.writeString(emailAddress)
parcel.writeString(webAddress)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<AboutPerson> {
override fun createFromParcel(parcel: Parcel): AboutPerson = AboutPerson(parcel)
override fun newArray(size: Int): Array<AboutPerson?> = arrayOfNulls(size)
}
}

View File

@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.content.Intent
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import androidx.appcompat.widget.TooltipCompat
import org.kde.kdeconnect.UserInterface.List.ListAdapter
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.AboutPersonListItemEntryBinding
class AboutPersonEntryItem(val person: AboutPerson) : ListAdapter.Item {
override fun inflateView(layoutInflater: LayoutInflater): View {
val binding = AboutPersonListItemEntryBinding.inflate(layoutInflater)
binding.aboutPersonListItemEntryName.text = person.name
if (person.task != null) {
binding.aboutPersonListItemEntryTask.visibility = View.VISIBLE
binding.aboutPersonListItemEntryTask.text = layoutInflater.context.getString(person.task)
}
if (person.emailAddress != null) {
binding.aboutPersonListItemEntryEmailButton.visibility = View.VISIBLE
TooltipCompat.setTooltipText(binding.aboutPersonListItemEntryEmailButton, layoutInflater.context.getString(R.string.email_contributor, person.emailAddress))
binding.aboutPersonListItemEntryEmailButton.setOnClickListener {
layoutInflater.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("mailto:" + person.emailAddress)))
}
}
if (person.webAddress != null) {
binding.aboutPersonListItemEntryVisitHomepageButton.visibility = View.VISIBLE
TooltipCompat.setTooltipText(binding.aboutPersonListItemEntryVisitHomepageButton, layoutInflater.context.resources.getString(R.string.visit_contributors_homepage, person.webAddress))
binding.aboutPersonListItemEntryVisitHomepageButton.setOnClickListener {
layoutInflater.context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(person.webAddress)))
}
}
return binding.root
}
}

View File

@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.content.Context
import android.database.DataSetObserver
import android.util.AttributeSet
import android.widget.Adapter
import android.widget.LinearLayout
class AdapterLinearLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : LinearLayout(context, attrs, defStyle) {
var adapter: Adapter? = null
set(adapter) {
if (field !== adapter) {
field = adapter
field?.registerDataSetObserver(dataSetObserver)
reloadChildViews()
}
}
private val dataSetObserver: DataSetObserver = object : DataSetObserver() {
override fun onChanged() {
super.onChanged()
reloadChildViews()
}
}
init {
orientation = VERTICAL
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
adapter?.unregisterDataSetObserver(dataSetObserver)
}
private fun reloadChildViews() {
removeAllViews()
if (adapter != null) {
for (position in 0 until adapter!!.count) {
adapter!!.getView(position, null, this)?.let { addView(it) }
}
requestLayout()
}
}
}

View File

@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.content.Context
import org.kde.kdeconnect_tp.BuildConfig
import org.kde.kdeconnect_tp.R
/**
* Add authors and credits here
*/
fun getApplicationAboutData(context: Context): AboutData {
val aboutData = AboutData(context.getString(R.string.kde_connect), R.string.app_description, R.drawable.icon, BuildConfig.VERSION_NAME, context.getString(R.string.copyright_statement),
context.getString(R.string.report_bug_url), context.getString(R.string.website_url), context.getString(R.string.source_code_url), context.getString(R.string.donate_url),
R.string.everyone_else)
aboutData.authors += AboutPerson("Albert Vaca Cintora", R.string.maintainer_and_developer, "albertvaka+kde@gmail.com")
aboutData.authors += AboutPerson("Aleix Pol", R.string.developer, "aleixpol@kde.org")
aboutData.authors += AboutPerson("Inoki Shaw", R.string.apple_support, "veyx.shaw@gmail.com")
aboutData.authors += AboutPerson("Matthijs Tijink", R.string.developer, "matthijstijink@gmail.com")
aboutData.authors += AboutPerson("Nicolas Fella", R.string.developer, "nicolas.fella@gmx.de")
aboutData.authors += AboutPerson("Philip Cohn-Cort", R.string.developer, "cliabhach@gmail.com")
aboutData.authors += AboutPerson("Piyush Aggarwal", R.string.developer, "piyushaggarwal002@gmail.com")
aboutData.authors += AboutPerson("Simon Redman", R.string.developer, "simon@ergotech.com")
aboutData.authors += AboutPerson("Erik Duisters", R.string.developer, "e.duisters1@gmail.com")
aboutData.authors += AboutPerson("Isira Seneviratne", R.string.developer, "isirasen96@gmail.com")
aboutData.authors += AboutPerson("Vineet Garg", R.string.developer, "grg.vineet@gmail.com")
aboutData.authors += AboutPerson("Anjani Kumar", R.string.bug_fixes_and_general_improvements, "anjanik012@gmail.com")
aboutData.authors += AboutPerson("Samoilenko Yuri", R.string.samoilenko_yuri_task, "kinnalru@gmail.com")
aboutData.authors += AboutPerson("Aniket Kumar", R.string.aniket_kumar_task, "anikketkumar786@gmail.com")
aboutData.authors += AboutPerson("Àlex Fiestas", R.string.alex_fiestas_task, "afiestas@kde.org")
aboutData.authors += AboutPerson("Daniel Tang", R.string.bug_fixes_and_general_improvements, "danielzgtg.opensource@gmail.com")
aboutData.authors += AboutPerson("Maxim Leshchenko", R.string.maxim_leshchenko_task, "cnmaks90@gmail.com")
aboutData.authors += AboutPerson("Holger Kaelberer", R.string.holger_kaelberer_task, "holger.k@elberer.de")
aboutData.authors += AboutPerson("Saikrishna Arcot", R.string.saikrishna_arcot_task, "saiarcot895@gmail.com")
return aboutData
}

View File

@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.content.Context
import android.util.AttributeSet
import androidx.gridlayout.widget.GridLayout
import org.kde.kdeconnect_tp.R
import kotlin.math.max
/**
* GridLayout that adjusts the number of columns and rows to fill all screen space
*/
class AutoGridLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : GridLayout(context, attrs, defStyleAttr) {
private var defaultColumnCount = 0
private var columnWidth = 0
private var changeColumnCountIfTheyHaveOnlyOneElement = false
init {
var typedArray = context.obtainStyledAttributes(attrs, R.styleable.AutoGridLayout, 0, defStyleAttr)
try {
columnWidth = typedArray.getDimensionPixelSize(R.styleable.AutoGridLayout_columnWidth, 0)
changeColumnCountIfTheyHaveOnlyOneElement = typedArray.getBoolean(R.styleable.AutoGridLayout_changeColumnCountIfTheyHaveOnlyOneElement, false)
typedArray = context.obtainStyledAttributes(attrs, intArrayOf(android.R.attr.columnCount), 0, defStyleAttr)
defaultColumnCount = typedArray.getInt(0, 10)
} finally {
typedArray.recycle()
}
columnCount = 1
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
super.onMeasure(widthSpec, heightSpec)
val width = MeasureSpec.getSize(widthSpec)
if (columnWidth > 0 && width > 0) {
val totalSpace = width - paddingRight - paddingLeft
var calculatedColumnCount = max(1, totalSpace / columnWidth)
if (calculatedColumnCount < childCount && changeColumnCountIfTheyHaveOnlyOneElement) {
calculatedColumnCount = defaultColumnCount
}
columnCount = calculatedColumnCount
} else {
columnCount = defaultColumnCount
}
}
}

View File

@ -0,0 +1,175 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.animation.ValueAnimator
import android.content.pm.ActivityInfo
import android.graphics.Color
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.ActivityEasterEggBinding
import kotlin.math.PI
import kotlin.math.atan2
private val KDE_ICON_BACKGROUND_COLOR = Color.rgb(29, 153, 243)
private val KONQI_BACKGROUND_COLOR = Color.rgb(191, 255, 0)
class EasterEggActivity : AppCompatActivity(), SensorEventListener {
private var binding: ActivityEasterEggBinding? = null
private lateinit var sensorManager: SensorManager
private val animator = ValueAnimator()
private var isAlreadyLongClicked = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEasterEggBinding.inflate(layoutInflater)
setContentView(binding!!.root)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
// Make the status bar blue to make the Easter Egg beautiful
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = KDE_ICON_BACKGROUND_COLOR
window.navigationBarColor = KDE_ICON_BACKGROUND_COLOR
setLightSystemWindowsEnabled(false)
}
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
if (hasAccelerometer()) {
animator.addUpdateListener {
binding!!.logo.rotation = animator.animatedValue as Float
}
animator.duration = 300
} else {
binding!!.angle.visibility = View.GONE
}
// Make Easter Egg more fun
binding!!.easterEggLayout.setOnLongClickListener {
if (!isAlreadyLongClicked) {
isAlreadyLongClicked = true
binding!!.easterEggLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.activity_background))
binding!!.logo.setColorFilter(ContextCompat.getColor(this, R.color.text_color))
binding!!.angle.setTextColor(ContextCompat.getColor(this, R.color.text_color))
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
var typedArray = this.theme.obtainStyledAttributes(intArrayOf(android.R.attr.statusBarColor))
window.statusBarColor = typedArray.getColor(0, Color.WHITE)
window.navigationBarColor = typedArray.getColor(0, Color.WHITE)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
typedArray = this.theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowLightStatusBar))
setLightSystemWindowsEnabled(typedArray.getBoolean(0, true))
}
typedArray.recycle()
}
}
val icon = intArrayOf(
R.drawable.ic_action_keyboard_24dp, R.drawable.ic_action_refresh_24dp,
R.drawable.ic_action_image_edit_24dp, R.drawable.ic_arrow_upward_black_24dp,
R.drawable.ic_baseline_attach_money_24, R.drawable.ic_baseline_bug_report_24,
R.drawable.ic_baseline_code_24, R.drawable.ic_baseline_gavel_24,
R.drawable.ic_baseline_info_24, R.drawable.ic_baseline_web_24,
R.drawable.ic_baseline_send_24, R.drawable.ic_baseline_sms_24,
R.drawable.ic_accept_pairing_24dp, R.drawable.ic_share_white,
R.drawable.ic_camera, R.drawable.ic_delete,
R.drawable.ic_device_laptop_32dp, R.drawable.ic_device_phone_32dp,
R.drawable.ic_device_tablet_32dp, R.drawable.ic_device_tv_32dp,
R.drawable.ic_delete, R.drawable.ic_warning,
R.drawable.ic_volume_black, R.drawable.ic_wifi,
R.drawable.ic_add, R.drawable.touchpad_plugin_action_24dp,
R.drawable.konqi, R.drawable.run_command_plugin_icon_24dp,
R.drawable.ic_phonelink_36dp, R.drawable.ic_phonelink_off_36dp,
R.drawable.ic_error_outline_48dp, R.drawable.ic_home_black_24dp,
R.drawable.ic_settings_white_32dp, R.drawable.ic_stop,
R.drawable.ic_rewind_black, R.drawable.ic_play_black,
R.drawable.ic_mic_black, R.drawable.ic_pause_black,
R.drawable.ic_volume_mute_black, R.drawable.ic_arrow_upward_black_24dp,
R.drawable.ic_next_black, R.drawable.ic_previous_black,
R.drawable.ic_presenter_24dp, R.drawable.ic_key,
R.drawable.ic_keyboard_return_black_24dp, R.drawable.ic_keyboard_hide_36dp,
R.drawable.ic_kde_24dp, R.drawable.ic_album_art_placeholder,
R.drawable.ic_arrow_back_black_24dp, R.drawable.share_plugin_action_24dp
).random()
if (icon == R.drawable.konqi) {
binding!!.logo.clearColorFilter()
binding!!.easterEggLayout.setBackgroundColor(KONQI_BACKGROUND_COLOR)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
window.statusBarColor = KONQI_BACKGROUND_COLOR
window.navigationBarColor = KONQI_BACKGROUND_COLOR
}
isAlreadyLongClicked = false
}
binding!!.logo.setImageResource(icon)
true
}
}
private fun hasAccelerometer(): Boolean = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null
private fun setLightSystemWindowsEnabled(enabled: Boolean) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (enabled) {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
if (enabled) {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
} else {
window.decorView.systemUiVisibility = window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
}
}
}
override fun onResume() {
super.onResume()
if (hasAccelerometer()) {
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_UI)
}
}
override fun onPause() {
if (hasAccelerometer()) {
sensorManager.unregisterListener(this)
}
super.onPause()
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { }
override fun onSensorChanged(event: SensorEvent) {
if (binding != null) {
val axisX = event.values[0]
val axisY = event.values[1]
val angle = (atan2(axisX, axisY) / (PI / 180)).toInt()
binding!!.angle.text = angle.toString() + "°"
animator.setFloatValues(binding!!.logo.rotation, angle.toFloat())
animator.start()
}
}
}

View File

@ -0,0 +1,67 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import org.apache.commons.io.IOUtils
import org.kde.kdeconnect.UserInterface.ThemeUtil
import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.ActivityLicensesBinding
import java.nio.charset.Charset
class LicensesActivity : AppCompatActivity() {
private lateinit var binding: ActivityLicensesBinding
override fun onCreate(savedInstanceState: Bundle?) {
ThemeUtil.setUserPreferredTheme(this)
super.onCreate(savedInstanceState)
binding = ActivityLicensesBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbarLayout.toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar!!.setDisplayShowHomeEnabled(true)
binding.licensesText.layoutManager = LinearLayoutManager(this)
binding.licensesText.adapter = StringListAdapter(getLicenses().split("\n\n"))
}
private fun getLicenses(): String = resources.openRawResource(R.raw.license).use { inputStream -> IOUtils.toString(inputStream, Charset.defaultCharset()) }
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_licenses, menu)
return super.onCreateOptionsMenu(menu)
}
private fun smoothScrollToPosition(position: Int) {
val linearSmoothScroller: LinearSmoothScroller = object : LinearSmoothScroller(this) {
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float = 2.5F / displayMetrics.densityDpi
}
linearSmoothScroller.targetPosition = position
binding.licensesText.layoutManager?.startSmoothScroll(linearSmoothScroller)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.menu_rise_up -> {
smoothScrollToPosition(0)
true
}
R.id.menu_rise_down -> {
smoothScrollToPosition(binding.licensesText.adapter!!.itemCount - 1)
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2021 Maxim Leshchenko <cnmaks90@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
package org.kde.kdeconnect.UserInterface.About
import android.widget.TextView
import android.view.View
import android.view.ViewGroup
import androidx.core.view.setPadding
import androidx.recyclerview.widget.RecyclerView
class StringListAdapter(private val stringList: List<String>) : RecyclerView.Adapter<StringListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val textView = TextView(parent.context)
textView.setPadding(8)
return ViewHolder(textView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
(holder.itemView as TextView).text = stringList[position]
}
override fun getItemCount(): Int = stringList.size
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view)
}

View File

@ -23,10 +23,16 @@ public class ListAdapter extends ArrayAdapter<ListAdapter.Item> {
} }
private final List<? extends Item> items; private final List<? extends Item> items;
private final boolean isEnabled;
public ListAdapter(Context context, List<? extends Item> items) { public ListAdapter(Context context, List<? extends Item> items) {
this(context, items, true);
}
public ListAdapter(Context context, List<? extends Item> items, boolean isEnabled) {
super(context, 0, (List<Item>) items); super(context, 0, (List<Item>) items);
this.items = items; this.items = items;
this.isEnabled = isEnabled;
} }
@NonNull @NonNull
@ -35,4 +41,9 @@ public class ListAdapter extends ArrayAdapter<ListAdapter.Item> {
final Item i = items.get(position); final Item i = items.get(position);
return i.inflateView(LayoutInflater.from(parent.getContext())); return i.inflateView(LayoutInflater.from(parent.getContext()));
} }
@Override
public boolean isEnabled(int position) {
return isEnabled;
}
} }

View File

@ -22,7 +22,6 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@ -35,16 +34,20 @@ import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment; import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment;
import org.kde.kdeconnect.UserInterface.About.AboutFragment;
import org.kde.kdeconnect.UserInterface.About.ApplicationAboutDataKt;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding; import org.kde.kdeconnect_tp.databinding.ActivityMainBinding;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Objects;
public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener { public class MainActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final int MENU_ENTRY_ADD_DEVICE = 1; //0 means no-selection private static final int MENU_ENTRY_ADD_DEVICE = 1; //0 means no-selection
private static final int MENU_ENTRY_SETTINGS = 2; private static final int MENU_ENTRY_SETTINGS = 2;
private static final int MENU_ENTRY_ABOUT = 3;
private static final int MENU_ENTRY_DEVICE_FIRST_ID = 1000; //All subsequent ids are devices in the menu private static final int MENU_ENTRY_DEVICE_FIRST_ID = 1000; //All subsequent ids are devices in the menu
private static final int MENU_ENTRY_DEVICE_UNKNOWN = 9999; //It's still a device, but we don't know which one yet private static final int MENU_ENTRY_DEVICE_UNKNOWN = 9999; //It's still a device, but we don't know which one yet
private static final int STORAGE_lOCATION_CONFIGURED = 2020; private static final int STORAGE_lOCATION_CONFIGURED = 2020;
@ -129,6 +132,11 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply(); preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
setContentFragment(new SettingsFragment()); setContentFragment(new SettingsFragment());
break; break;
case MENU_ENTRY_ABOUT:
mCurrentDevice = null;
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
setContentFragment(AboutFragment.newInstance(Objects.requireNonNull(ApplicationAboutDataKt.getApplicationAboutData(this))));
break;
default: default:
String deviceId = mMapMenuToDeviceId.get(menuItem); String deviceId = mMapMenuToDeviceId.get(menuItem);
onDeviceSelected(deviceId); onDeviceSelected(deviceId);
@ -187,6 +195,8 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
} else { } else {
if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS) { if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS) {
setContentFragment(new SettingsFragment()); setContentFragment(new SettingsFragment());
} else if (mCurrentMenuEntry == MENU_ENTRY_ABOUT) {
setContentFragment(AboutFragment.newInstance(Objects.requireNonNull(ApplicationAboutDataKt.getApplicationAboutData(this))));
} else { } else {
setContentFragment(new PairingFragment()); setContentFragment(new PairingFragment());
} }
@ -282,6 +292,10 @@ public class MainActivity extends AppCompatActivity implements SharedPreferences
settingsItem.setIcon(R.drawable.ic_settings_white_32dp); settingsItem.setIcon(R.drawable.ic_settings_white_32dp);
settingsItem.setCheckable(true); settingsItem.setCheckable(true);
MenuItem aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about);
aboutItem.setIcon(R.drawable.ic_baseline_info_24);
aboutItem.setCheckable(true);
//Ids might have changed //Ids might have changed
if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) { if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) {
mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice); mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice);

View File

@ -1,6 +1,5 @@
package org.kde.kdeconnect.UserInterface; package org.kde.kdeconnect.UserInterface;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
@ -32,33 +31,6 @@ import java.nio.charset.Charset;
public class SettingsFragment extends PreferenceFragmentCompat { public class SettingsFragment extends PreferenceFragmentCompat {
private static class LicenseTextSingleton {
private static volatile String licenseText;
// Need these separate methods because Context is required to load the string
// Mixing Context and static here is fine because the license
// is a pure function of the project directory (i.e. doesn't depend on language, etc.)
static synchronized String getOrLoadLicenseText(Context context) {
String licenseText = LicenseTextSingleton.licenseText;
if (licenseText != null) {
return licenseText;
}
try (InputStream is = context.getResources().openRawResource(R.raw.license)) {
licenseText = IOUtils.toString(is, Charset.defaultCharset());
} catch (IOException ie) {
throw new RuntimeException(ie);
}
return LicenseTextSingleton.licenseText = licenseText;
}
static synchronized void startLoadingLicenseText(Context context) {
if (licenseText == null) {
new Thread(() -> getOrLoadLicenseText(context)).start();
}
}
}
private EditTextPreference renameDevice; private EditTextPreference renameDevice;
@Override @Override
@ -187,40 +159,6 @@ public class SettingsFragment extends PreferenceFragmentCompat {
moreSettingsText.setSummary(R.string.settings_more_settings_text); moreSettingsText.setSummary(R.string.settings_more_settings_text);
screen.addPreference(moreSettingsText); screen.addPreference(moreSettingsText);
Preference bug = new Preference(context);
bug.setTitle(R.string.report_bug);
bug.setPersistent(false);
bug.setIcon(R.drawable.ic_baseline_bug_report_24);
bug.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.report_bug_url))));
screen.addPreference(bug);
Preference donate = new Preference(context);
donate.setTitle(R.string.donate);
donate.setPersistent(false);
donate.setIcon(R.drawable.ic_baseline_attach_money_24);
donate.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.donate_url))));
screen.addPreference(donate);
Preference source = new Preference(context);
source.setTitle(R.string.source_code);
source.setPersistent(false);
source.setIcon(R.drawable.ic_baseline_code_24);
source.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.source_code_url))));
screen.addPreference(source);
Preference licences = new Preference(context);
licences.setTitle(R.string.licenses);
licences.setPersistent(false);
licences.setIcon(R.drawable.ic_baseline_gavel_24);
licences.setOnPreferenceClickListener(preference -> {
new AlertDialog.Builder(context)
.setMessage(LicenseTextSingleton.getOrLoadLicenseText(context))
.create().show();
return true;
});
LicenseTextSingleton.startLoadingLicenseText(context);
screen.addPreference(licences);
setPreferenceScreen(screen); setPreferenceScreen(screen);