WIP: [android] convert help screen to compose #5179

Draft
arnaudvergnet wants to merge 1 commit from arnaudvergnet/compose into master
12 changed files with 673 additions and 586 deletions

View file

@ -25,6 +25,10 @@ buildscript {
// Add a parameter to force Firebase.
ext.googleFirebaseServicesEnabled = project.hasProperty('firebase') ?: googleFirebaseServicesDefault
// See https://developer.android.com/jetpack/androidx/releases/compose-kotlin
// to see which kotlin version is compatible with compose
ext.kotlin_version = '1.8.21'
dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
@ -45,6 +49,8 @@ buildscript {
classpath('com.github.triplet.gradle:play-publisher:3.8.3')
classpath('ru.cian:huawei-publish-gradle-plugin:1.4.0')
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -65,6 +71,7 @@ if (googleFirebaseServicesEnabled) {
}
apply plugin: 'com.github.triplet.play'
apply plugin: 'ru.cian.huawei-publish-gradle-plugin'
apply plugin: 'kotlin-android'
dependencies {
// Google Mobile Services
@ -82,10 +89,30 @@ dependencies {
implementation 'com.google.firebase:firebase-crashlytics-ndk'
}
// This line is added as a workaround for duplicate classes error caused by some outdated dependency:
// > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
// We don't use Kotlin, but some dependencies are actively using it.
implementation(platform('org.jetbrains.kotlin:kotlin-bom:1.8.21'))
// Kotlin
implementation 'androidx.core:core-ktx:1.10.1'
implementation(platform("org.jetbrains.kotlin:kotlin-bom:$kotlin_version"))
// Dependencies for Jetpack Compose
// See https://developer.android.com/jetpack/compose/setup#setup-compose
def composeBom = platform('androidx.compose:compose-bom:2023.04.01')
implementation composeBom
androidTestImplementation composeBom
// Material Design 2
implementation 'androidx.compose.material:material'
// Android Studio Preview support
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
// Optional - Integration with activities
implementation 'androidx.activity:activity-compose:1.7.1'
// Optional - Integration with ViewModels
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
// Optional - Integration with LiveData
implementation 'androidx.compose.runtime:runtime-livedata'
// Optional - Allows controlling status bar and navigation bar
implementation "com.google.accompanist:accompanist-systemuicontroller:0.31.2-alpha"
implementation 'androidx.annotation:annotation:1.6.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
@ -139,6 +166,15 @@ android {
buildFeatures {
dataBinding = true
compose true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.7"
}
// All properties are read from gradle.properties file
compileSdkVersion propCompileSdkVersion.toInteger()

View file

@ -4,7 +4,7 @@ propCompileSdkVersion=33
propBuildToolsVersion=34.0.0
org.gradle.caching=true
org.gradle.jvmargs=-Xmx1024m -Xms256m
org.gradle.jvmargs=-Xmx2048m -Xms256m
android.useAndroidX=true
android.native.buildOutput=verbose
android.defaults.buildfeatures.buildconfig=true

View file

@ -1,170 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_base"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
tools:text="2021.10.15-15-Google" />
<TextView
android:id="@+id/data_version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
tools:text="OpenStreetMap Data: 3 January 2022" />
</LinearLayout>
</LinearLayout>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginBottom="@dimen/margin_base"
android:layout_marginStart="@dimen/margin_base"
android:layout_marginEnd="@dimen/margin_base"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:text="@string/about_description"/>
<TextView
android:id="@+id/donate"
style="@style/MwmWidget.TextView.Item"
android:text="@string/donate"
app:drawableStartCompat="@drawable/ic_donate" />
<TextView android:id="@+id/report"
style="@style/MwmWidget.TextView.Item"
android:text="@string/report_a_bug"
app:drawableStartCompat="@drawable/ic_report_a_bug"/>
<TextView
android:id="@+id/support_us"
style="@style/MwmWidget.TextView.Item"
android:text="@string/how_to_support_us"
app:drawableStartCompat="@drawable/ic_contribute" />
<TextView android:id="@+id/faq"
style="@style/MwmWidget.TextView.Item"
android:text="@string/faq"
app:drawableStartCompat="@drawable/ic_question_mark"/>
<TextView android:id="@+id/news"
style="@style/MwmWidget.TextView.Item"
android:text="@string/news"
app:drawableStartCompat="@drawable/ic_news"/>
<TextView
android:id="@+id/rate"
style="@style/MwmWidget.TextView.Item"
android:text="@string/rate_the_app"
app:drawableStartCompat="@drawable/ic_rate" />
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/margin_quarter"
android:layout_marginBottom="@dimen/margin_quarter"
android:background="?dividerHorizontal"/>
<TextView android:id="@+id/telegram"
style="@style/MwmWidget.TextView.Item"
android:text="@string/telegram"
app:drawableStartCompat="@drawable/ic_telegram" />
<TextView android:id="@+id/github"
style="@style/MwmWidget.TextView.Item"
android:text="@string/github"
app:drawableStartCompat="@drawable/ic_github"/>
<TextView android:id="@+id/web"
style="@style/MwmWidget.TextView.Item"
android:text="@string/website"
app:drawableStartCompat="@drawable/ic_website"/>
<TextView android:id="@+id/email"
style="@style/MwmWidget.TextView.Item"
android:text="@string/email"
app:drawableStartCompat="@drawable/ic_email"/>
<TextView android:id="@+id/matrix"
style="@style/MwmWidget.TextView.Item"
android:text="@string/matrix"
app:drawableStartCompat="@drawable/ic_matrix"/>
<TextView android:id="@+id/mastodon"
style="@style/MwmWidget.TextView.Item"
android:text="@string/mastodon"
app:drawableStartCompat="@drawable/ic_mastodon"/>
<TextView android:id="@+id/facebook"
style="@style/MwmWidget.TextView.Item"
android:text="@string/facebook"
app:drawableStartCompat="@drawable/ic_facebook"/>
<TextView android:id="@+id/twitter"
style="@style/MwmWidget.TextView.Item"
android:text="@string/twitter"
app:drawableStartCompat="@drawable/ic_twitterx"/>
<TextView android:id="@+id/instagram"
style="@style/MwmWidget.TextView.Item"
android:text="@string/instagram"
app:drawableStartCompat="@drawable/ic_instagram"/>
<TextView android:id="@+id/openstreetmap"
style="@style/MwmWidget.TextView.Item"
android:text="@string/openstreetmap"
app:drawableStartCompat="@drawable/ic_openstreetmap"/>
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/margin_quarter"
android:layout_marginBottom="@dimen/margin_quarter"
android:background="?dividerHorizontal"/>
<TextView android:id="@+id/privacy_policy"
style="@style/MwmWidget.TextView.Item"
android:text="@string/privacy_policy"/>
<TextView android:id="@+id/term_of_use_link"
style="@style/MwmWidget.TextView.Item"
android:text="@string/terms_of_use"/>
<TextView android:id="@+id/copyright"
style="@style/MwmWidget.TextView.Item"
android:text="@string/copyright"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<include layout="@layout/shadow_top"/>
</FrameLayout>

View file

@ -1,242 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?cardBackground"
android:fadeScrollbars="false"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:clipToPadding="false"
android:orientation="vertical"
android:padding="@dimen/margin_base">
<ImageView
android:layout_width="@dimen/about_logo"
android:layout_height="@dimen/about_logo"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/margin_base"
android:layout_marginBottom="@dimen/margin_half"
android:contentDescription="@string/app_name"
android:src="@drawable/logo"
app:tint="?attr/colorLogo" />
<TextView
android:id="@+id/version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:layout_marginBottom="@dimen/margin_half"
android:selectAllOnFocus="true"
android:textAppearance="@style/MwmTextAppearance.Body3"
android:textIsSelectable="true"
tools:text="2021.10.15-15-Google" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_base"
android:text="@string/about_headline"
android:textAlignment="center"
android:textAppearance="@style/MwmTextAppearance.Headline"
android:textColor="?android:textColorPrimary" />
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/margin_half"
android:text="@string/about_proposition_1"
android:textAppearance="@style/MwmTextAppearance.Body1"
android:textColor="?android:textColorPrimary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_half"
android:text="@string/about_proposition_2"
android:textAppearance="@style/MwmTextAppearance.Body1"
android:textColor="?android:textColorPrimary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_half"
android:text="@string/about_proposition_3"
android:textAppearance="@style/MwmTextAppearance.Body1"
android:textColor="?android:textColorPrimary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/margin_half"
android:fontFamily="@string/robotoRegular"
android:text="@string/about_developed_by_enthusiasts"
android:textAppearance="@style/MwmTextAppearance.Body2"
android:textColor="?android:textColorPrimary"
android:textStyle="bold" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/osm_logo"
android:layout_width="@dimen/osm_logo"
android:layout_height="@dimen/osm_logo"
android:layout_gravity="center"
android:contentDescription="@string/openstreetmap"
android:src="@drawable/ic_openstreetmap_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/osm_presentation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_half"
android:fontFamily="@string/robotoRegular"
android:includeFontPadding="false"
android:text="@string/osm_presentation"
android:textAppearance="@style/MwmTextAppearance.Body2"
android:textColor="?android:textColorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/osm_logo"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/donate"
style="@style/MwmWidget.Button.Accent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_base"
android:fontFamily="@string/robotoMedium"
android:padding="@dimen/margin_quarter"
android:text="@string/donate" />
<Button
android:id="@+id/report"
style="@style/MwmWidget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_base"
android:background="@color/light_gray"
android:fontFamily="@string/robotoMedium"
android:text="@string/report_a_bug"
android:textColor="@color/text_dark" />
<TextView
android:id="@+id/support_us"
style="@style/MwmWidget.TextView.Item"
android:text="@string/how_to_support_us"
app:drawableStartCompat="@drawable/ic_donate" />
<TextView
android:id="@+id/faq"
style="@style/MwmWidget.TextView.Item"
android:text="@string/faq"
app:drawableStartCompat="@drawable/ic_question_mark" />
<TextView
android:id="@+id/news"
style="@style/MwmWidget.TextView.Item"
android:text="@string/news"
app:drawableStartCompat="@drawable/ic_news" />
<TextView
android:id="@+id/rate"
style="@style/MwmWidget.TextView.Item"
android:text="@string/rate_the_app"
app:drawableStartCompat="@drawable/ic_rate" />
<TextView
android:id="@+id/telegram"
style="@style/MwmWidget.TextView.Item"
android:text="@string/telegram"
app:drawableStartCompat="@drawable/ic_telegram" />
<TextView
android:id="@+id/github"
style="@style/MwmWidget.TextView.Item"
android:text="@string/github"
app:drawableStartCompat="@drawable/ic_github" />
<TextView
android:id="@+id/web"
style="@style/MwmWidget.TextView.Item"
android:text="@string/website"
app:drawableStartCompat="@drawable/ic_website" />
<TextView
android:id="@+id/email"
style="@style/MwmWidget.TextView.Item"
android:text="@string/email"
app:drawableStartCompat="@drawable/ic_email" />
<TextView
android:id="@+id/matrix"
style="@style/MwmWidget.TextView.Item"
android:text="@string/matrix"
app:drawableStartCompat="@drawable/ic_matrix" />
<TextView
android:id="@+id/mastodon"
style="@style/MwmWidget.TextView.Item"
android:text="@string/mastodon"
app:drawableStartCompat="@drawable/ic_mastodon" />
<TextView
android:id="@+id/facebook"
style="@style/MwmWidget.TextView.Item"
android:text="@string/facebook"
app:drawableStartCompat="@drawable/ic_facebook" />
<TextView
android:id="@+id/twitter"
style="@style/MwmWidget.TextView.Item"
android:text="@string/twitter"
app:drawableStartCompat="@drawable/ic_twitterx" />
<TextView
android:id="@+id/instagram"
style="@style/MwmWidget.TextView.Item"
android:text="@string/instagram"
app:drawableStartCompat="@drawable/ic_instagram" />
<TextView
android:id="@+id/openstreetmap"
style="@style/MwmWidget.TextView.Item"
android:text="@string/openstreetmap"
app:drawableStartCompat="@drawable/ic_openstreetmap" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="@dimen/margin_quarter"
android:layout_marginTop="@dimen/margin_quarter"
android:background="?dividerHorizontal" />
<TextView
android:id="@+id/privacy_policy"
style="@style/MwmWidget.TextView.Item"
android:text="@string/privacy_policy" />
<TextView
android:id="@+id/term_of_use_link"
style="@style/MwmWidget.TextView.Item"
android:text="@string/terms_of_use" />
<TextView
android:id="@+id/copyright"
style="@style/MwmWidget.TextView.Item"
android:text="@string/copyright" />
</LinearLayout>
</ScrollView>

View file

@ -40,7 +40,7 @@ public class CopyrightFragment extends BaseMwmFragment implements OnBackPressLis
{
if (!mDelegate.onBackPressed())
{
((HelpActivity) requireActivity()).stackFragment(HelpFragment.class, getString(R.string.help), null);
// ((HelpActivity) requireActivity()).stackFragment(HelpFragment.class, getString(R.string.help), null);
}
return true;

View file

@ -1,14 +0,0 @@
package app.organicmaps.help;
import androidx.fragment.app.Fragment;
import app.organicmaps.base.BaseToolbarActivity;
public class HelpActivity extends BaseToolbarActivity
{
@Override
protected Class<? extends Fragment> getFragmentClass()
{
return HelpFragment.class;
}
}

View file

@ -0,0 +1,100 @@
package app.organicmaps.help
import android.app.Activity
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import app.organicmaps.R
import app.organicmaps.util.Theme
class HelpActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelpContent()
}
}
}
@Composable
private fun HelpContent() {
val context = LocalContext.current
Theme {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(
stringResource(id = R.string.about_menu_title)
)
},
navigationIcon = {
IconButton(onClick = { (context as Activity).finish() }) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
},
)
},
) { contentPadding ->
Box(
Modifier
.padding(contentPadding)
.verticalScroll(
rememberScrollState()
)
) {
Column(
Modifier
.padding(dimensionResource(id = R.dimen.margin_base))
) {
Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.margin_base)))
HelpHeader()
HelpItemList(Modifier.padding(top = dimensionResource(id = R.dimen.margin_half)))
}
}
}
}
}
@Preview(
name = "dark theme",
uiMode = UI_MODE_NIGHT_YES
)
@Preview(name = "light theme")
@Preview(
name = "landscape",
device = "spec:width=700dp,height=300dp,dpi=480,orientation=landscape"
)
annotation class HelpPreviews
@HelpPreviews
@Composable
private fun HelpPreview() {
HelpContent()
}

View file

@ -1,154 +0,0 @@
package app.organicmaps.help;
import android.content.res.Configuration;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.organicmaps.BuildConfig;
import app.organicmaps.Framework;
import app.organicmaps.R;
import app.organicmaps.base.BaseMwmFragment;
import app.organicmaps.util.Config;
import app.organicmaps.util.Constants;
import app.organicmaps.util.DateUtils;
import app.organicmaps.util.Graphics;
import app.organicmaps.util.Utils;
public class HelpFragment extends BaseMwmFragment implements View.OnClickListener
{
private String mDonateUrl;
private TextView setupItem(@IdRes int id, boolean tint, @NonNull View frame)
{
final TextView view = frame.findViewById(id);
view.setOnClickListener(this);
if (tint)
Graphics.tint(view);
return view;
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
{
mDonateUrl = Config.getDonateUrl();
if (TextUtils.isEmpty(mDonateUrl) && !BuildConfig.FLAVOR.equals("google") && !BuildConfig.FLAVOR.equals("huawei"))
mDonateUrl = getResources().getString(R.string.translated_om_site_url) + "donate/";
View root = inflater.inflate(R.layout.about, container, false);
((TextView) root.findViewById(R.id.version))
.setText(BuildConfig.VERSION_NAME);
final boolean isLandscape = getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
final String dataVersion = DateUtils.getShortDateFormatter().format(Framework.getDataVersion());
final TextView dataVersionView = (TextView) root.findViewById(R.id.data_version);
if (dataVersionView != null)
dataVersionView.setText(getString(R.string.data_version, dataVersion));
final TextView osmPresentationView = (TextView) root.findViewById(R.id.osm_presentation);
if (osmPresentationView != null)
osmPresentationView.setText(getString(R.string.osm_presentation, dataVersion));
setupItem(R.id.news, true, root);
setupItem(R.id.web, true, root);
setupItem(R.id.email, true, root);
setupItem(R.id.github, true, root);
setupItem(R.id.telegram, false, root);
setupItem(R.id.instagram, false, root);
setupItem(R.id.facebook, false, root);
setupItem(R.id.twitter, true, root);
setupItem(R.id.matrix, true, root);
setupItem(R.id.mastodon, false, root);
setupItem(R.id.openstreetmap, true, root);
setupItem(R.id.faq, true, root);
setupItem(R.id.report, isLandscape, root);
final TextView supportUsView = root.findViewById(R.id.support_us);
if (BuildConfig.FLAVOR.equals("google") && !TextUtils.isEmpty(mDonateUrl))
supportUsView.setVisibility(View.GONE);
else
setupItem(R.id.support_us, true, root);
final TextView donateView = root.findViewById(R.id.donate);
if (TextUtils.isEmpty(mDonateUrl))
donateView.setVisibility(View.GONE);
else
setupItem(R.id.donate, isLandscape, root);
if (BuildConfig.REVIEW_URL.isEmpty())
root.findViewById(R.id.rate).setVisibility(View.GONE);
else
setupItem(R.id.rate, true, root);
setupItem(R.id.copyright, false, root);
View termOfUseView = root.findViewById(R.id.term_of_use_link);
View privacyPolicyView = root.findViewById(R.id.privacy_policy);
termOfUseView.setOnClickListener(v -> onTermOfUseClick());
privacyPolicyView.setOnClickListener(v -> onPrivacyPolicyClick());
return root;
}
private void openLink(@NonNull String link)
{
Utils.openUrl(requireActivity(), link);
}
private void onPrivacyPolicyClick()
{
openLink(getResources().getString(R.string.translated_om_site_url) + "privacy/");
}
private void onTermOfUseClick()
{
openLink(getResources().getString(R.string.translated_om_site_url) + "terms/");
}
@Override
public void onClick(View v)
{
final int id = v.getId();
if (id == R.id.web)
openLink(getResources().getString(R.string.translated_om_site_url));
else if (id == R.id.news)
openLink(getResources().getString(R.string.translated_om_site_url) + "news/");
else if (id == R.id.email)
Utils.sendTo(requireContext(), BuildConfig.SUPPORT_MAIL, "Organic Maps");
else if (id == R.id.github)
openLink(Constants.Url.GITHUB);
else if (id == R.id.telegram)
openLink(getString(R.string.telegram_url));
else if (id == R.id.instagram)
openLink(getString(R.string.instagram_url));
else if (id == R.id.facebook)
Utils.showFacebookPage(requireActivity());
else if (id == R.id.twitter)
openLink(Constants.Url.TWITTER);
else if (id == R.id.matrix)
openLink(Constants.Url.MATRIX);
else if (id == R.id.mastodon)
openLink(Constants.Url.MASTODON);
else if (id == R.id.openstreetmap)
openLink(getString(R.string.osm_wiki_about_url));
else if (id == R.id.faq)
((HelpActivity) requireActivity()).stackFragment(FaqFragment.class, getString(R.string.faq), null);
else if (id == R.id.report)
Utils.sendBugReport(requireActivity(), "");
else if (id == R.id.support_us)
openLink(getResources().getString(R.string.translated_om_site_url) + "support-us/");
else if (id == R.id.donate)
openLink(mDonateUrl);
else if (id == R.id.rate)
Utils.openAppInMarket(requireActivity(), BuildConfig.REVIEW_URL);
else if (id == R.id.copyright)
((HelpActivity) requireActivity()).stackFragment(CopyrightFragment.class, getString(R.string.copyright), null);
}
}

View file

@ -0,0 +1,165 @@
package app.organicmaps.help
import android.app.Activity
import android.text.TextUtils
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import app.organicmaps.BuildConfig
import app.organicmaps.Framework
import app.organicmaps.R
import app.organicmaps.util.Config
import app.organicmaps.util.DateUtils
import app.organicmaps.util.ExtendedMaterialTheme
import app.organicmaps.util.Theme
import app.organicmaps.util.Utils
@Composable
fun HelpHeader() {
BoxWithConstraints {
if (maxWidth < 400.dp) {
Column {
HelpHeaderLeft()
HelpHeaderRight()
}
} else {
Row {
HelpHeaderLeft(Modifier.weight(1f))
HelpHeaderRight(Modifier.weight(1f))
}
}
}
}
@Composable
fun HelpHeaderLeft(modifier: Modifier = Modifier) {
Column(modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Icon(
painter = painterResource(R.drawable.logo),
contentDescription = stringResource(id = R.string.app_name),
tint = ExtendedMaterialTheme.colors.colorLogo,
modifier = Modifier
.size(96.dp)
)
SelectionContainer {
Text(
BuildConfig.VERSION_NAME,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = dimensionResource(id = R.dimen.margin_half)),
textAlign = TextAlign.Center,
style = ExtendedMaterialTheme.typography.caption
)
}
Text(
stringResource(id = R.string.about_headline),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(id = R.dimen.margin_base)),
textAlign = TextAlign.Center,
style = ExtendedMaterialTheme.typography.h6
)
Text(
stringResource(id = R.string.about_proposition_1),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(id = R.dimen.margin_half)),
)
Text(
stringResource(id = R.string.about_proposition_2),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(id = R.dimen.margin_half)),
)
Text(
stringResource(id = R.string.about_proposition_3),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(id = R.dimen.margin_half)),
)
}
}
@Composable
fun HelpHeaderRight(modifier: Modifier = Modifier) {
val context = LocalContext.current
val omWebsite = stringResource(id = R.string.translated_om_site_url)
// Do not read the config while in the preview
var donateUrl = if (!LocalInspectionMode.current) Config.getDonateUrl() else ""
if (TextUtils.isEmpty(donateUrl) && BuildConfig.FLAVOR != "google" && BuildConfig.FLAVOR != "huawei")
donateUrl = omWebsite + "donate/"
val dataVersion = if (!LocalInspectionMode.current) DateUtils.getShortDateFormatter()
.format(Framework.getDataVersion()) else "DATA_VERSION"
Column(modifier) {
Text(
stringResource(id = R.string.about_developed_by_enthusiasts),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = dimensionResource(id = R.dimen.margin_half)),
fontWeight = FontWeight.Bold
)
Row(verticalAlignment = Alignment.CenterVertically) {
Image(
painter = painterResource(id = R.drawable.ic_openstreetmap_color),
contentDescription = stringResource(id = R.string.openstreetmap),
modifier = Modifier
.size(dimensionResource(id = R.dimen.osm_logo))
)
Text(
stringResource(id = R.string.osm_presentation, dataVersion),
modifier = Modifier.padding(start = dimensionResource(id = R.dimen.margin_half)),
)
}
Button(
onClick = { Utils.openUrl(context, donateUrl) },
colors = ButtonDefaults.buttonColors(backgroundColor = ExtendedMaterialTheme.colors.material.secondary),
modifier = Modifier
.fillMaxWidth()
.padding(top = dimensionResource(id = R.dimen.margin_half))
) {
Text(stringResource(id = R.string.donate))
}
OutlinedButton(
onClick = { Utils.sendBugReport(context as Activity, "") },
colors = ButtonDefaults.buttonColors(backgroundColor = ExtendedMaterialTheme.colors.material.surface),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(id = R.string.report_a_bug))
}
}
}
@HelpPreviews
@Composable
private fun HelpHeaderPreview() {
Theme {
Surface {
HelpHeader()
}
}
}

View file

@ -0,0 +1,232 @@
package app.organicmaps.help
import android.app.Activity
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import app.organicmaps.BuildConfig
import app.organicmaps.R
import app.organicmaps.util.Constants
import app.organicmaps.util.ExtendedMaterialTheme
import app.organicmaps.util.Theme
import app.organicmaps.util.Utils
@Composable
fun HelpItemList(modifier: Modifier = Modifier) {
val context = LocalContext.current
val omWebsite = stringResource(id = R.string.translated_om_site_url)
fun openUrl(url: String) {
Utils.openUrl(context, url)
}
Column(modifier) {
BoxWithConstraints {
if (maxWidth < 400.dp) {
Column {
HelpItemsLeft()
HelpItemsRight()
}
} else {
Row {
HelpItemsLeft(Modifier.weight(1f))
HelpItemsRight(Modifier.weight(1f))
}
}
}
Divider()
HelpItem(
text = stringResource(id = R.string.privacy_policy),
onClick = { openUrl(omWebsite + "privacy/") }
)
HelpItem(
text = stringResource(id = R.string.terms_of_use),
onClick = { openUrl(omWebsite + "terms/") }
)
HelpItem(
text = stringResource(id = R.string.copyright),
onClick = {} // TODO migrate that screen to compose as well
)
}
}
@Composable
fun HelpItemsLeft(modifier: Modifier = Modifier) {
val context = LocalContext.current
val omWebsite = stringResource(id = R.string.translated_om_site_url)
val telegram = stringResource(id = R.string.telegram_url)
fun openUrl(url: String) {
Utils.openUrl(context, url)
}
Column(modifier) {
HelpItem(
icon = painterResource(id = R.drawable.ic_donate),
tintIcon = true,
text = stringResource(id = R.string.how_to_support_us),
onClick = { Utils.openUrl(context, omWebsite + "support-us/") },
)
HelpItem(
icon = painterResource(id = R.drawable.ic_question_mark),
tintIcon = true,
text = stringResource(id = R.string.faq),
onClick = {} // TODO migrate that screen to compose as well
)
HelpItem(
icon = painterResource(id = R.drawable.ic_news),
tintIcon = true,
text = stringResource(id = R.string.news),
onClick = { openUrl(omWebsite + "news/") },
)
HelpItem(
icon = painterResource(id = R.drawable.ic_rate),
tintIcon = true,
text = stringResource(id = R.string.rate_the_app),
onClick = { Utils.openAppInMarket(context as Activity, BuildConfig.REVIEW_URL) },
)
HelpItem(
icon = painterResource(id = R.drawable.ic_telegram),
text = stringResource(id = R.string.telegram),
onClick = { openUrl(telegram) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_github),
tintIcon = true,
text = stringResource(id = R.string.github),
onClick = { openUrl(Constants.Url.GITHUB) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_website),
tintIcon = true,
text = stringResource(id = R.string.website),
onClick = { openUrl(omWebsite) }
)
}
}
@Composable
fun HelpItemsRight(modifier: Modifier = Modifier) {
val context = LocalContext.current
val instagram = stringResource(id = R.string.instagram_url)
val osm = stringResource(id = R.string.osm_wiki_about_url)
fun openUrl(url: String) {
Utils.openUrl(context, url)
}
Column(modifier) {
HelpItem(
icon = painterResource(id = R.drawable.ic_email),
tintIcon = true,
text = stringResource(id = R.string.email),
onClick = { Utils.sendTo(context, BuildConfig.SUPPORT_MAIL, "Organic Maps") }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_matrix),
tintIcon = true,
text = stringResource(id = R.string.matrix),
onClick = { openUrl(Constants.Url.MATRIX) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_mastodon),
text = stringResource(id = R.string.mastodon),
onClick = { openUrl(Constants.Url.MASTODON) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_facebook),
text = stringResource(id = R.string.facebook),
onClick = { Utils.showFacebookPage(context as Activity) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_twitterx),
tintIcon = true,
text = stringResource(id = R.string.twitter),
onClick = { openUrl(Constants.Url.TWITTER) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_instagram),
text = stringResource(id = R.string.instagram),
onClick = { openUrl(instagram) }
)
HelpItem(
icon = painterResource(id = R.drawable.ic_openstreetmap),
tintIcon = true,
text = stringResource(id = R.string.openstreetmap),
onClick = { openUrl(osm) }
)
}
}
@Composable
fun HelpItem(
text: String,
modifier: Modifier = Modifier,
icon: Painter? = null,
tintIcon: Boolean = false,
onClick: () -> Unit = {},
) {
Box(modifier.clickable { onClick() }) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(dimensionResource(id = R.dimen.height_item_oneline))
.padding(horizontal = dimensionResource(id = R.dimen.margin_base))
.fillMaxWidth()
) {
if (icon != null) Image(
icon, contentDescription = "",
colorFilter = if (tintIcon) ColorFilter.tint(ExtendedMaterialTheme.colors.iconTint) else null
)
Text(
text,
modifier = if (icon != null) Modifier.padding(start = dimensionResource(id = R.dimen.margin_base_plus)) else Modifier
)
}
}
}
@Preview
@Composable
private fun HelpItemPreview() {
Theme {
Surface {
HelpItem(
icon = painterResource(id = R.drawable.ic_openstreetmap),
tintIcon = true,
text = stringResource(id = R.string.openstreetmap),
onClick = { /* Nothing */ }
)
}
}
}
@HelpPreviews
@Composable
private fun HelpHeaderPreview() {
Theme {
Surface {
HelpItemList()
}
}
}

View file

@ -0,0 +1,21 @@
package app.organicmaps.util
import androidx.compose.ui.graphics.Color
// Light theme
val color_primary = Color(0xFF006C35)
val color_on_primary = Color(0xFFFFFFFF)
val color_secondary = Color(0xFF249CF2)
val color_on_secondary = Color(0xFFFFFFFF)
val color_icon_tint = Color(0x8A000000)
val color_logo = color_primary
val color_status_bar = color_primary
// Dark theme
val color_primary_dark = Color(0xFF1E2226)
val color_on_primary_dark = Color(0xFF000000)
val color_secondary_dark = Color(0xFF4BB9E6)
val color_on_secondary_dark = Color(0xFF000000)
val color_icon_tint_dark = Color(0xB3FFFFFF)
val color_logo_dark = Color(0xFFFFFFFF)
val color_status_bar_dark = Color(0xFF272727)

View file

@ -0,0 +1,113 @@
package app.organicmaps.util
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Shapes
import androidx.compose.material.Typography
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import com.google.accompanist.systemuicontroller.rememberSystemUiController
// **********************************
// Overwrite material theme colors
// **********************************
private val LightColors = lightColors(
primary = color_primary,
onPrimary = color_on_primary,
secondary = color_secondary,
onSecondary = color_on_secondary,
)
private val DarkColors = darkColors(
primary = color_primary_dark,
onPrimary = color_on_primary_dark,
secondary = color_secondary_dark,
onSecondary = color_on_secondary_dark,
)
// **********************************
// Define new theme colors
// **********************************
// Defines a new class to hold material colors along with new ones
// This is only to define the type, for the values see below
@Immutable
data class ExtendedColors(
val material: Colors = LightColors,
val iconTint: Color = Color.Unspecified,
val colorLogo: Color = Color.Unspecified,
val statusBar: Color = Color.Unspecified,
)
// Define the value for the new colors
private val LightExtendedColors = ExtendedColors(
material = LightColors,
iconTint = color_icon_tint,
colorLogo = color_logo,
statusBar = color_status_bar
)
private val DarkExtendedColors = ExtendedColors(
material = DarkColors,
iconTint = color_icon_tint_dark,
colorLogo = color_logo_dark,
statusBar = color_status_bar_dark
)
val LocalExtendedColors = staticCompositionLocalOf {
ExtendedColors()
}
// Create a new theme object extending the material theme with our own colors
// We can use this object to access our custom colors or default Material properties
object ExtendedMaterialTheme {
val colors: ExtendedColors
@Composable
get() = LocalExtendedColors.current
val shapes: Shapes
@Composable
get() = MaterialTheme.shapes
val typography: Typography
@Composable
get() = MaterialTheme.typography
}
// **********************************
// Create the actual theme composable
// **********************************
@Composable
fun Theme(
content: @Composable () -> Unit
) {
// Do not use ThemeUtils if the component is running in a preview window
val nightMode = if (LocalInspectionMode.current)
isSystemInDarkTheme()
else
ThemeUtils.isNightTheme(LocalContext.current)
val systemUiController = rememberSystemUiController()
// Return our extended theme for use in our custom components
CompositionLocalProvider(LocalExtendedColors provides if (nightMode) DarkExtendedColors else LightExtendedColors) {
val statusBarColor = ExtendedMaterialTheme.colors.statusBar
DisposableEffect(systemUiController, statusBarColor) {
systemUiController.setStatusBarColor(
color = statusBarColor,
darkIcons = false
)
onDispose {}
}
// Return MaterialTheme so base components can read our overwritten properties
MaterialTheme(
colors = LocalExtendedColors.current.material,
content = content
)
}
}